> For the complete documentation index, see [llms.txt](https://docs.gmgn.ai/cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gmgn.ai/cn/gmgn-callout-openapi.md).

# GMGN Callout OpenAPI

GMGN Callout OpenAPI 允许已获批准的合作伙伴在自己的平台发布和读取 GMGN 的 Callouts——这是来自钱包的信号，表明该钱包正在喊单某个代币，发送 Callout 徐附带观点内容以及关联的 Twitter/X 身份。

申请表格：<https://docs.google.com/forms/d/e/1FAIpQLScRnqI0PSQBEewJnF3JnfRSm50PylZRcIAfE7bl96Yp7RCQCg/viewform?usp=dialog>

所有接口均为 `POST`,请求与响应均为 `application/json`(UTF-8),且每次请求都需 **AK/SK 签名鉴权**,并按 `ak` 维度限频。`ak` / `sk` 由我方分配。

|                  |                                           |
| ---------------- | ----------------------------------------- |
| **Base URL**     | `https://papi.gmgn.ai/callout/openapi/v1` |
| **Content-Type** | `application/json`(UTF-8)                 |
| **鉴权**           | AK/SK HMAC-SHA256 签名(每次请求带请求头)            |

### 从哪个接口开始

| 接口            | 读写     | 提供什么                                    |
| ------------- | ------ | --------------------------------------- |
| `/global`     | **只读** | 全链全钱包的所有喊单,最新在前。**接入最简单**——除游标外无任何筛选参数。 |
| `/token`      | 只读     | 单个代币的跨钱包喊单列表,含最高倍率喊单与付费置顶宣言。            |
| `/get_record` | 只读     | 单个钱包的喊单历史;也是**唯一**回显喊单真实来源的接口。          |
| `/create`     | 写      | 发起喊单,或对他人喊单发起跟喊(Callback)。              |

若只需消费全量数据流(社群机器人、信息流、看板),**只用 `/global` 一个接口即可**。

***

### POST /global — 全局喊单流(只读)

跨所有链、跨所有钱包的全局喊单流,按时间**严格倒序**(最新在前),游标向更早翻页。面向社群 / 机器人 / 信息流的只读接入场景。

```
POST https://papi.gmgn.ai/callout/openapi/v1/global
```

#### 四条固定约束

以下约束**由服务端固定,无法通过任何入参调整**:

|  #  | 约束              | 说明                                                                           |
| :-: | --------------- | ---------------------------------------------------------------------------- |
|  1  | **只返回最近 7 天数据** | 翻到 7 天前边界即停止返回 `next_page_token`,分页自然终止。                                     |
|  2  | **限频 1 QPS**    | per-AK,与其余接口共享的 \~10 QPS 组**独立计数**。见限频。                                      |
|  3  | **不支持筛选**       | 服务端**忽略**你传的任何 `chains` / `only_kol` / `only_declaration` / `limit`,恒返回全链全量。 |
|  4  | **单页固定 100 条**  | 页大小不可调。                                                                      |

#### 请求体

| 字段           | 类型     |  必填 | 说明                                      |
| ------------ | ------ | :-: | --------------------------------------- |
| `page_token` | string |     | 翻页游标(上一页返回的 `next_page_token`),首页留空或不传。 |

> ℹ️ **除 `page_token` 外的任何字段均被服务端忽略**——全局流不接受筛选。空 body / 畸形 body 一律按首页处理,返回 `200`,**不返回 400**。

#### 成功响应

```json
{
  "code": 0,
  "data": {
    "records": [
      {
        "chain": "sol",
        "call_wallet": "...",
        "call_token": "...",
        "call_price": "0.5",
        "call_mc": "500",
        "ulid": "01HF7...",
        "create_time": 1786300000000,
        "token_symbol": "TKN",
        "token_logo": "...",
        "multiplier": "3.2",
        "twitter_username": "...",
        "twitter_handle": "...",
        "holding_percentage": "0.12",
        "source": "gmgn",
        "is_kol": true,
        "is_declaration": true
      }
    ],
    "next_page_token": "eyJ..."
  }
}
```

#### `records[]` 字段

| 字段                   | 类型     | 说明                                                                      |
| -------------------- | ------ | ----------------------------------------------------------------------- |
| `chain`              | string | 链。                                                                      |
| `call_wallet`        | string | 发起喊单的钱包。                                                                |
| `call_token`         | string | 喊单代币地址。                                                                 |
| `call_price`         | string | 喊单时价格。                                                                  |
| `call_mc`            | string | 喊单时市值。                                                                  |
| `ulid`               | string | 喊单唯一标识。                                                                 |
| `create_time`        | int64  | 喊单时间,Unix **毫秒**。                                                       |
| `token_symbol`       | string | 代币符号。                                                                   |
| `token_logo`         | string | 代币 logo URL。                                                            |
| `multiplier`         | string | 相对喊单价的当前倍率。                                                             |
| `twitter_username`   | string | 喊单者显示名。                                                                 |
| `twitter_handle`     | string | 喊单者 `@handle`。                                                          |
| `holding_percentage` | string | 喊单者对该代币的持仓百分比。                                                          |
| `source`             | string | 喊单来源:gmgn 原生喊单为设备名(`web` / `android` / `ios`),第三方平台喊单为其平台标识。            |
| `is_kol`             | bool   | 该喊单钱包是否为 GMGN 标记的 KOL 钱包(KOL 自行绑定推特的钱包,属公开数据)。**恒返回** `true` / `false`。 |
| `is_declaration`     | bool   | 是否付费宣言喊单。`omitempty`——**仅为 `true` 时出现**,字段缺失即视为 `false`。                |

#### 排序与翻页

* 记录按 `create_time`(及 `ulid`)**严格降序**,跨链归并,翻页**不重不漏**。
* 传 `page_token` = 上一页的 `next_page_token` 拉取下一(更早)页。
* **`next_page_token` 为空或不返回,表示已翻至最近 7 天边界或数据翻尽**,停止翻页。
* 其余 `omitempty` 字段(如 `callback_*`)无值时不出现。

> ℹ️ 本接口只给**布尔标记** `is_declaration`,**不返回**宣言金额 `amount_usd` / 发起时间等付费明细。如需请另议。

***

### 统一响应信封

所有接口返回 `{code, message, data}` 结构。**业务数据在 `data` 层,客户端须解包 `data`,不要直接读顶层字段。**

```json
{ "code": 0, "message": "", "data": { } }
```

* **`HTTP 200` + `code = 0`** —— 请求成功,业务体在 `data`。
* **`HTTP 4xx / 5xx`** —— `code`(int)+ `message`(string)描述错误,无 `data`。

#### 写接口的两层结果

对 `POST /create` 而言,传输层的 `HTTP 200` **并不代表**喊单已被受理。结果分两层:

1. **传输 / 鉴权 / 限频层** —— HTTP 状态码(`400`、`401`、`429`、`500`)。
2. **业务规则层** —— `data` 内的布尔字段 `data.success`。为 `false` 时看 `data.reject.reason`。

#### 数据类型

* **价格、市值、倍率、百分比、付费金额均为字符串**(如 `"0.5"`、`"3.2"`),以保留精度。请用十进制安全的库解析,不要用原生浮点。
* **时间戳为 Unix 毫秒**(int64),除非某字段明确为 ISO-8601 字符串(如 `/token` 列表里的 `created_at`)。
* **`ulid`** 是喊单的唯一标识。
* 标注 `omitempty` 的字段,无值时**整个 key 不出现**——不要假设 key 一定存在。

***

### 鉴权:AK/SK 签名

每次请求须用 **AK/SK** 凭证做 **HMAC-SHA256** 签名。`sk` 只保存在服务端——切勿放进客户端 App 或浏览器。

#### 必带请求头

| 请求头           | 说明                               |
| ------------- | -------------------------------- |
| `X-Ak`        | 分配的 access key。                  |
| `X-Timestamp` | 当前时间戳,**毫秒**(Unix milliseconds)。 |
| `X-Signature` | 对下方负载做 HMAC-SHA256 的十六进制签名(小写)。  |

#### 签名负载

按顺序**直接字符串相接(无分隔符)**:

```
payload = ak + timestamp + method + path + rawQuery + body
```

| 字段          | 说明                                                            |
| ----------- | ------------------------------------------------------------- |
| `ak`        | 同 `X-Ak`。                                                     |
| `timestamp` | 同 `X-Timestamp`(毫秒字符串)。                                       |
| `method`    | HTTP 方法大写,如 `POST`。                                           |
| `path`      | 请求路径,**含 API 前缀,不含域名与 query**,如 `/callout/openapi/v1/global`。 |
| `rawQuery`  | URL 查询串(这些 POST 接口通常为空串)。                                     |
| `body`      | 请求体原文,**须与实际发送字节完全一致**;无则空串。                                  |

```
signature = HEX( HMAC_SHA256(sk, payload) )   // 小写十六进制
```

> ⚠️ **时间戳窗口 ±30 秒**:`X-Timestamp` 与服务器时间偏差超过 30s 将被拒,请确保客户端时钟同步(NTP)。
>
> ℹ️ 签名覆盖的是 **body 原始字节**,请对实际发送的 JSON 字符串签名。签名与发送之间不要重新序列化、美化格式或重排 key。

#### 签名示例

<details>

<summary>Go</summary>

```go
ts := strconv.FormatInt(time.Now().UnixMilli(), 10)
method := "POST"
path := "/callout/openapi/v1/global"
rawQuery := ""
body := `{"page_token":""}`

payload := ak + ts + method + path + rawQuery + body
mac := hmac.New(sha256.New, []byte(sk))
mac.Write([]byte(payload))
sig := hex.EncodeToString(mac.Sum(nil))
// 请求头:X-Ak: ak, X-Timestamp: ts, X-Signature: sig
```

</details>

<details>

<summary>Python</summary>

```python
import hashlib, hmac, time, json, requests

AK, SK = "your_ak", "your_sk"
BASE = "https://papi.gmgn.ai"
path = "/callout/openapi/v1/global"
method = "POST"
raw_query = ""
body = json.dumps({"page_token": ""}, separators=(",", ":"))  # 对实际发送的字节签名

ts = str(int(time.time() * 1000))
payload = AK + ts + method + path + raw_query + body
sig = hmac.new(SK.encode(), payload.encode(), hashlib.sha256).hexdigest()

resp = requests.post(
    BASE + path,
    data=body,  # 发送与签名相同的字节
    headers={
        "Content-Type": "application/json",
        "X-Ak": AK,
        "X-Timestamp": ts,
        "X-Signature": sig,
    },
)
print(resp.json())
```

</details>

<details>

<summary>Node.js</summary>

```javascript
import crypto from "node:crypto";

const AK = "your_ak", SK = "your_sk";
const BASE = "https://papi.gmgn.ai";
const path = "/callout/openapi/v1/global";
const method = "POST";
const rawQuery = "";
const body = JSON.stringify({ page_token: "" }); // 对实际发送的字符串签名

const ts = Date.now().toString();
const payload = AK + ts + method + path + rawQuery + body;
const sig = crypto.createHmac("sha256", SK).update(payload).digest("hex");

const resp = await fetch(BASE + path, {
  method,
  headers: {
    "Content-Type": "application/json",
    "X-Ak": AK,
    "X-Timestamp": ts,
    "X-Signature": sig,
  },
  body,
});
console.log(await resp.json());
```

</details>

#### 鉴权失败(HTTP 401)

```json
{ "code": 401, "message": "invalid signature" }
```

| `message`             | 含义                                          |
| --------------------- | ------------------------------------------- |
| `missing auth header` | `X-Ak` / `X-Timestamp` / `X-Signature` 缺其一。 |
| `invalid timestamp`   | `X-Timestamp` 格式错误或超出 ±30s 窗口。              |
| `invalid ak`          | access key 无法识别。                            |
| `invalid signature`   | 计算出的签名不匹配。                                  |
| `api not allowed`     | 该 `ak` 无权调用此接口。                             |
| `ip not allowed`      | 请求来自非白名单 IP。                                |

***

### 限频

限频按 **`ak` 维度**生效。

| 限制             | 限额                   | 超限响应                                 | 作用接口                             |
| -------------- | -------------------- | ------------------------------------ | -------------------------------- |
| 请求速率(QPS)      | 以分配为准(默认约 10 QPS)    | `HTTP 429` `rate limit exceeded`     | `/create`、`/get_record`、`/token` |
| **全局流速率(QPS)** | **1 QPS**(严格,per-AK) | `HTTP 429` `rate limit exceeded`     | 仅 `/global`                      |
| 喊单 24h 配额      | 以分配为准                | `HTTP 429` `ak daily quota exceeded` | 仅 `/create`                      |

* 查询接口(`/global`、`/get_record`、`/token`)**不占**喊单 24h 配额。
* **`/global` 有独立的 1 QPS 桶**,与其余接口共享的 \~10 QPS 组分开计数;且只读最近 7 天、不支持任何筛选,见四条固定约束。
* 24h 喊单配额与「按 `twitter_id`、按钱包」的业务上限是不同的限制:后者以**业务被拒**(`data.success = false`)返回,而非 `HTTP 429`——见拒绝原因。

***

### POST /token — 代币页跨钱包喊单

按 `chain` + `call_token` 返回该代币下跨所有钱包的喊单列表 + 最高倍率喊单(`top_message`)+ 付费置顶宣言(`vip_message`)+ 游标翻页。

```
POST https://papi.gmgn.ai/callout/openapi/v1/token
```

#### 请求体

| 字段           | 类型     |  必填 | 说明                                            |
| ------------ | ------ | :-: | --------------------------------------------- |
| `chain`      | string |  ✅  | 链。                                            |
| `call_token` | string |  ✅  | 代币地址。                                         |
| `cursor`     | string |     | 翻页游标,首页留空。                                    |
| `limit`      | int    |     | 每页条数,**上限 50**;传 `<= 0` 或 `> 50` 均**封顶为 50**。 |

> ⚠️ 注意与 `/get_record` 的差异:本接口对超范围 `limit` **封顶 50**,而 `/get_record` 是**回退 20**。

#### 成功响应

```json
{
  "code": 0,
  "data": {
    "chain": "sol",
    "community": { "token_address": "..." },
    "messages": [
      {
        "id": "gmgn_01HF7...",
        "content": "...",
        "display_content": "...",
        "username": "...",
        "display_name": "...",
        "profile_image_url": "...",
        "wallet_address": "...",
        "user_twitter_url": "...",
        "follower_count": 0,
        "like_count": 0,
        "reply_count": 0,
        "created_at": "2026-07-14T00:00:00Z",
        "source": "gmgn",
        "multiplier": "2.3",
        "ulid": "01HF7...",
        "callback_count": 0,
        "callback_to_ulid": "",
        "callback_to_chain": "",
        "callback_to_wallet": ""
      }
    ],
    "has_more": true,
    "top_message": { },
    "vip_message": { },
    "declaration_level": "199",
    "next_cursor": "..."
  }
}
```

#### `messages[]` 字段

| 字段                   | 类型            | 说明                                                        |
| -------------------- | ------------- | --------------------------------------------------------- |
| `id`                 | string        | 消息标识。                                                     |
| `content`            | string        | 喊单理由 / 文案(源文)。                                            |
| `display_content`    | string        | 译文文案。**本接口恒为空**——openapi 场景强制空 `app_lang`,仅出源文 `content`。 |
| `media_url`          | string / null | 附带媒体(如有)。                                                 |
| `username`           | string        | 喊单者 handle。                                               |
| `display_name`       | string        | 喊单者显示名。                                                   |
| `profile_image_url`  | string / null | 喊单者头像 URL。                                                |
| `wallet_address`     | string        | 喊单者钱包地址。                                                  |
| `user_twitter_url`   | string        | 喊单者 Twitter/X 主页 URL。                                     |
| `follower_count`     | int64         | 喊单者粉丝数。                                                   |
| `like_count`         | int64         | 点赞数。**当前恒为 0**(pump 下线后为兼容既有前端结构保留)。                      |
| `reply_count`        | int64         | 回复数。**当前恒为 0**(同上)。                                       |
| `created_at`         | string        | 喊单的 ISO-8601 时间。                                          |
| `source`             | string / null | 本接口**恒为 `gmgn`**——见下方说明。                                  |
| `multiplier`         | string        | 相对喊单价的倍率。                                                 |
| `ulid`               | string        | 喊单唯一标识。                                                   |
| `callback_count`     | int           | 该帖被跟喊次数。                                                  |
| `callback_to_ulid`   | string        | 被跟喊原帖的 ULID。                                              |
| `callback_to_chain`  | string        | 被跟喊原帖的链。                                                  |
| `callback_to_wallet` | string        | 被跟喊原帖的钱包。                                                 |

#### 跟喊(Callback)语义

`callback_count` 在**原创帖与跟喊帖上都有**,表示该帖被跟喊的次数。`callback_to_*` 三个字段指向被跟喊的原帖;**三者同时为空表示该记录是原创帖,不是跟喊**。均为 `omitempty`。

#### `top_message` —— 最高倍率喊单(Best Callout)

近 **30 天**该代币倍率最高的喊单(按 decimal 比较取最高 `multiplier`)。

> ⚠️ **当前实现为每页均返回**,不区分首页 / 翻页。窗口内无喊单时该字段不出现。

#### `vip_message` —— 付费置顶宣言

查 `callout-declaration` 的 `success` 充值记录组装,结构同 `messages[]` 项,额外带:

| 字段           | 类型     | 说明                                                  |
| ------------ | ------ | --------------------------------------------------- |
| `amount_usd` | string | 付费档位金额。**仅** `vip_message` **填充**,`top_message` 不填。 |

* `omitempty`——无 `success` 记录时该字段不出现。
* 与 `top_message` **并列,可同时出现**。

> ℹ️ `vip_message.amount_usd` 属付费信息,本接口会**原样对外透传**给第三方合作方。

#### `declaration_level`

该代币当前充值档位。这是**顶层态(描述该代币)**,不是某条宣言的属性。一期为默认值 `"199"`,为后续档位扩展 / 竞价起拍价预留。

#### 翻页

* `has_more`(bool)是是否继续翻页的**权威判据**。
* `next_cursor` **仅当 `has_more` 为 `true` 时返回**;无更多页时该字段**整体省略**(不会返回空串或 null)。

> ⚠️ **请以 `has_more` 判断是否继续翻页,不要依赖 `next_cursor` 是否存在。**
>
> ℹ️ 本接口 `source` **恒为 `gmgn`**——代币页聚合视图不透传第三方来源标识。若需按喊单来源区分平台,请改用 `/get_record`。

***

### POST /get\_record — 按钱包查喊单历史

按钱包地址查该钱包的喊单历史,时间倒序,游标向更早翻页。

```
POST https://papi.gmgn.ai/callout/openapi/v1/get_record
```

#### 请求体

| 字段            | 类型     |  必填 | 说明                                   |
| ------------- | ------ | :-: | ------------------------------------ |
| `chain`       | string |  ✅  | 链。                                   |
| `call_wallet` | string |  ✅  | 要查的钱包地址。                             |
| `limit`       | int    |     | 每页条数,**取值须在 1\~50 之间**。              |
| `page_token`  | string |     | 翻页游标(上一页返回的 `next_page_token`),首页留空。 |

> ⚠️ **`limit` 超范围会回退为默认 20,而不是 50。** 传 `limit: 100` 只会拿到 **20** 条,不是 50 条;传 `<= 0` 同理。想要每页 50 条,必须精确传 `50`。(与 `/token` 的封顶 50 行为不同。)

#### 成功响应

```json
{
  "code": 0,
  "data": {
    "records": [
      {
        "chain": "sol",
        "call_wallet": "...",
        "call_token": "...",
        "call_price": "0.5",
        "call_mc": "500",
        "ulid": "01HF7...",
        "multiplier": "2.3",
        "token_symbol": "TKN",
        "token_logo": "...",
        "twitter_username": "...",
        "holding_percentage": "0.12",
        "source": "gmgn",
        "callback_count": 0,
        "callback_to_ulid": "",
        "callback_to_chain": "",
        "callback_to_wallet": ""
      }
    ],
    "next_page_token": "01HF6..."
  }
}
```

#### `records[]` 字段

| 字段                                                              | 类型     | 说明                    |
| --------------------------------------------------------------- | ------ | --------------------- |
| `chain`                                                         | string | 链。                    |
| `call_wallet`                                                   | string | 发起喊单的钱包。              |
| `call_token`                                                    | string | 喊单代币地址。               |
| `call_price`                                                    | string | 喊单时价格。                |
| `call_mc`                                                       | string | 喊单时市值。                |
| `ulid`                                                          | string | 喊单唯一标识。               |
| `multiplier`                                                    | string | 相对喊单价的当前倍率。           |
| `token_symbol`                                                  | string | 代币符号。                 |
| `token_logo`                                                    | string | 代币 logo URL。          |
| `twitter_username`                                              | string | 喊单者显示名。               |
| `holding_percentage`                                            | string | 喊单者对该代币的持仓百分比。        |
| `source`                                                        | string | **喊单真实来源**——见下方说明。    |
| `callback_count`                                                | int    | 该帖被跟喊次数。              |
| `callback_to_ulid` / `callback_to_chain` / `callback_to_wallet` | string | 指向被跟喊的原帖。`omitempty`。 |

#### `source` —— 唯一能区分真实平台的接口

`source` 回显喊单的**真实来源**:gmgn 原生喊单为设备名(`web` / `android` / `ios`),第三方平台喊单为其平台标识。

> ℹ️ **本接口按 `call_wallet` 回显真实来源,可据此区分平台。** `/token` 接口的 `source` 恒为 `gmgn`,不透传第三方标识。

#### 跟喊(Callback)语义

同 `/token`,见跟喊(Callback)语义。所有 `callback_*` 字段均为 `omitempty`。

#### 翻页

传 `page_token` = 上一页的 `next_page_token` 拉取下一(更早)页。`next_page_token` 为**空串**表示无更早记录。

***

### POST /create — 生成喊单

一个链上钱包对某代币发起喊单。来源标识由 `ak` 自动关联,无需传入。

```
POST https://papi.gmgn.ai/callout/openapi/v1/create
```

#### 请求体

| 字段                   | 类型     |  必填 | 说明                                            |
| -------------------- | ------ | :-: | --------------------------------------------- |
| `chain`              | string |  ✅  | 链,如 `sol` / `eth` / `bsc`。                    |
| `call_wallet`        | string |  ✅  | 发起喊单的链上钱包地址。                                  |
| `call_token`         | string |  ✅  | 喊单代币地址。                                       |
| `twitter_id`         | string |  ✅  | 喊单者 Twitter/X `rest_id`,**纯数字串**(`^[0-9]+$`)。 |
| `twitter_handle`     | string |     | @handle,展示兜底。                                 |
| `twitter_username`   | string |     | 显示名,展示兜底。                                     |
| `call_thesis`        | string |     | 喊单理由文案。**会做敏感词审核。**                           |
| `callback_to_chain`  | string |     | 跟喊:被跟喊原帖的链。                                   |
| `callback_to_wallet` | string |     | 跟喊:被跟喊原帖的钱包。                                  |
| `callback_to_ulid`   | string |     | 跟喊:被跟喊原帖的 ULID。                               |

> ℹ️ 三个 `callback_to_*` 字段描述一次**跟喊**(对另一条喊单的响应)。需**一起给**,或都不给。

#### 成功响应(HTTP 200)

```json
{
  "code": 0,
  "data": {
    "success": true,
    "record": {
      "chain": "sol",
      "call_wallet": "...",
      "call_token": "...",
      "call_price": "0.5",
      "call_mc": "500",
      "ulid": "01HF7...",
      "create_time": 1700000000000,
      "token_symbol": "TKN",
      "multiplier": "1"
    }
  }
}
```

`ulid` 是喊单唯一标识。

#### 业务被拒(HTTP 200)

请求格式正确且已通过鉴权,但被业务规则拦截:

```json
{
  "code": 0,
  "data": {
    "success": false,
    "reject": {
      "code": 40002404,
      "reason": "cooldown",
      "next_available_at": 1700000600000,
      "interval_minutes": 10
    }
  }
}
```

> ⚠️ 请用 **`reason`**(语义串)判断拒绝类型,**不要**用 `code`(仅供排障参考)。

#### 拒绝原因

| `reason`                    | `code`   | 含义                                                              |
| --------------------------- | -------- | --------------------------------------------------------------- |
| `holding_too_low`           | 40002403 | 喊单钱包对该代币持仓低于阈值。                                                 |
| `cooldown`                  | 40002404 | 同钱包 + 同代币处于冷却期(`next_available_at` 毫秒时间、`interval_minutes` 时长)。 |
| `daily_limit`               | 40002405 | 24h 喊单次数超上限(`next_available_at` 为窗口重置时间)。                       |
| `twitter_daily_limit`       | 40002409 | 同 `twitter_id` 24h 喊单次数超上限(`next_available_at` 为窗口重置时间)。        |
| `content_violation`         | 40002407 | 喊单文案命中敏感词。                                                      |
| `content_audit_unavailable` | 40002408 | 敏感词审核服务不可用(兜底拒绝,可稍后重试)。                                         |
| `twitter_not_bound`         | 40002401 | 喊单钱包未绑定 Twitter。**当前不会触发**——见下方说明。                              |
| `twitter_not_verified`      | 40002406 | 绑定的 Twitter 未通过校验。**当前不会触发**——见下方说明。                            |

**`reject` 字段**

| 字段                  | 类型     | 说明                                                                    |
| ------------------- | ------ | --------------------------------------------------------------------- |
| `reason`            | string | 语义拒绝原因(用它判断)。                                                         |
| `code`              | int    | 业务码(仅排障)。                                                             |
| `next_available_at` | int64  | 时间类拒绝(`cooldown`、`daily_limit`、`twitter_daily_limit`):下次可喊时间,Unix 毫秒。 |
| `interval_minutes`  | int64  | `cooldown` 专用:冷却窗口时长(分钟)。                                             |

> ℹ️ **`twitter_not_bound` / `twitter_not_verified` 当前对外平台策略下不会触发。** 外部平台策略为 `require_twitter_bind=false`、`require_twitter_verified=false`,喊单不校验内部 Twitter 绑定关系——改由 `twitter_id` 必填 + `rest_id` 格式约束承担该职责。此两码为未来扩展预留,客户端可暂不为其编写处理分支。
>
> ℹ️ **代币安全校验(honeypot 等)当前暂未启用。** 曾按代币安全信息硬拦截,因新池子缺安全信息导致误拦已下线,改为免责声明方案。当前不会返回该类 `reason`,客户端无需为其编写处理分支(后续若恢复会另行通知)。

#### 参数非法(HTTP 400)

```json
{ "code": 400, "message": "invalid twitter_id: must be a numeric rest_id" }
```

缺 `twitter_id` 或非纯数字串返回上述错误;缺其他必填字段返回相应 `400`。

***

### HTTP 状态码

| 状态码   | 含义                                                    |
| ----- | ----------------------------------------------------- |
| `200` | 请求已处理;写接口请看 `data.success`。                           |
| `400` | 参数非法(缺失 / 格式错误的必填字段)。`/global` 不会返回此码——畸形 body 按首页处理。 |
| `401` | 签名鉴权失败。见鉴权失败。                                         |
| `429` | 限频或 24h 配额超限。见限频。                                     |
| `500` | 内部错误,可退避后重试。                                          |

***

### OpenAPI 3.0 规范

以下为完整 OAS 3.0 定义,可直接导入 Swagger UI / Redoc / Postman。

> ℹ️ `X-Signature` 的值须**每次请求现算**,不能在 Swagger/Postman 里写死。请用 pre-request 脚本对 `ak + timestamp + method + path + rawQuery + body` 生成签名。

```yaml
openapi: 3.0.3
info:
  title: GMGN Callout OpenAPI
  version: 1.0.0
servers:
  - url: https://papi.gmgn.ai/callout/openapi/v1

components:
  schemas:
    Reject:
      type: object
      properties:
        code: { type: integer, description: "业务码(排障参考,判断类型请用 reason)" }
        reason: { type: string, description: "拒绝原因(用此字段判断类型)", example: cooldown }
        next_available_at: { type: integer, format: int64, description: "下次可喊时间(毫秒)" }
        interval_minutes: { type: integer, format: int64 }
    CallOutRecord:
      type: object
      properties:
        chain: { type: string }
        call_wallet: { type: string }
        call_token: { type: string }
        call_price: { type: string }
        call_mc: { type: string }
        ulid: { type: string }
        create_time: { type: integer, format: int64 }
        token_symbol: { type: string }
        token_logo: { type: string }
        holding_percentage: { type: string }
        multiplier: { type: string }
        twitter_username: { type: string }
        source: { type: string, description: "喊单真实来源;gmgn 原生为设备名(web/android/ios),第三方为平台标识" }
        callback_count: { type: integer, description: "被跟喊次数" }
        callback_to_ulid: { type: string }
        callback_to_chain: { type: string }
        callback_to_wallet: { type: string }
    GlobalCallOutRecord:
      allOf:
        - $ref: '#/components/schemas/CallOutRecord'
        - type: object
          properties:
            twitter_handle: { type: string }
            is_kol: { type: boolean, description: "是否 KOL 钱包(恒返回)" }
            is_declaration: { type: boolean, description: "是否付费宣言喊单;omitempty,仅 true 时出现,缺失视为 false" }
    TokenCallOutMessage:
      type: object
      properties:
        id: { type: string }
        content: { type: string }
        display_content: { type: string, description: "译文(openapi 强制空 app_lang,仅出源文时为空)" }
        media_url: { type: string, nullable: true }
        username: { type: string }
        display_name: { type: string }
        profile_image_url: { type: string, nullable: true }
        wallet_address: { type: string }
        user_twitter_url: { type: string }
        follower_count: { type: integer, format: int64 }
        like_count: { type: integer, format: int64, description: "点赞数(pump 下线后当前恒 0)" }
        reply_count: { type: integer, format: int64, description: "回复数(pump 下线后当前恒 0)" }
        created_at: { type: string }
        source: { type: string, nullable: true, description: "/token 恒为 gmgn;按来源区分请用 /get_record" }
        multiplier: { type: string }
        ulid: { type: string }
        callback_count: { type: integer }
        callback_to_ulid: { type: string }
        callback_to_chain: { type: string }
        callback_to_wallet: { type: string }
        amount_usd: { type: string, description: "付费档位金额,仅 vip_message 填充;top_message 不填" }
  parameters:
    XAk:        { name: X-Ak,        in: header, required: true, schema: { type: string } }
    XTimestamp: { name: X-Timestamp, in: header, required: true, schema: { type: string }, description: "Unix 毫秒时间戳(±30s)" }
    XSignature: { name: X-Signature, in: header, required: true, schema: { type: string }, description: "hex(HMAC_SHA256(sk, ak+ts+method+path+rawQuery+body))" }

paths:
  /global:
    post:
      summary: 全局喊单流(只读,最近 7 天 / 1qps / 不筛选 / 单页 100)
      parameters:
        - $ref: '#/components/parameters/XAk'
        - $ref: '#/components/parameters/XTimestamp'
        - $ref: '#/components/parameters/XSignature'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                page_token: { type: string, description: "翻页游标,首页留空;其余字段一律被忽略" }
      responses:
        '200':
          description: 成功;records 按 create_time 严格倒序,仅最近 7 天,单页 100
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: { type: integer }
                  data:
                    type: object
                    properties:
                      records:
                        type: array
                        items: { $ref: '#/components/schemas/GlobalCallOutRecord' }
                      next_page_token: { type: string, description: "为空表示已翻至 7 天边界或数据翻尽" }
        '401': { description: 签名鉴权失败 }
        '429': { description: 限频(1 QPS) }
        '500': { description: 内部错误 }
  /token:
    post:
      summary: 代币页跨钱包喊单列表
      parameters:
        - $ref: '#/components/parameters/XAk'
        - $ref: '#/components/parameters/XTimestamp'
        - $ref: '#/components/parameters/XSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [chain, call_token]
              properties:
                chain: { type: string }
                call_token: { type: string }
                cursor: { type: string }
                limit: { type: integer, description: "上限 50;<=0 或 >50 封顶 50" }
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: { type: integer }
                  data:
                    type: object
                    properties:
                      chain: { type: string }
                      community:
                        type: object
                        properties:
                          token_address: { type: string }
                      messages:
                        type: array
                        items: { $ref: '#/components/schemas/TokenCallOutMessage' }
                      has_more: { type: boolean }
                      top_message: { $ref: '#/components/schemas/TokenCallOutMessage' }
                      vip_message: { $ref: '#/components/schemas/TokenCallOutMessage' }
                      declaration_level: { type: string }
                      next_cursor: { type: string }
        '401': { description: 签名鉴权失败 }
        '429': { description: 限频 }
        '500': { description: 内部错误 }
  /get_record:
    post:
      summary: 按钱包查喊单历史
      parameters:
        - $ref: '#/components/parameters/XAk'
        - $ref: '#/components/parameters/XTimestamp'
        - $ref: '#/components/parameters/XSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [chain, call_wallet]
              properties:
                chain: { type: string }
                call_wallet: { type: string }
                limit: { type: integer, description: "1~50;<=0 或 >50 回退默认 20" }
                page_token: { type: string }
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: { type: integer }
                  data:
                    type: object
                    properties:
                      records:
                        type: array
                        items: { $ref: '#/components/schemas/CallOutRecord' }
                      next_page_token: { type: string }
        '401': { description: 签名鉴权失败 }
        '429': { description: 限频 }
        '500': { description: 内部错误 }
  /create:
    post:
      summary: 生成喊单
      parameters:
        - $ref: '#/components/parameters/XAk'
        - $ref: '#/components/parameters/XTimestamp'
        - $ref: '#/components/parameters/XSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [chain, call_wallet, call_token, twitter_id]
              properties:
                chain: { type: string, example: sol }
                call_wallet: { type: string }
                call_token: { type: string }
                twitter_id: { type: string, pattern: '^[0-9]+$' }
                twitter_handle: { type: string }
                twitter_username: { type: string }
                call_thesis: { type: string }
                callback_to_chain: { type: string }
                callback_to_wallet: { type: string }
                callback_to_ulid: { type: string }
      responses:
        '200':
          description: 业务是否通过看 data.success
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: { type: integer }
                  data:
                    type: object
                    properties:
                      success: { type: boolean }
                      record: { $ref: '#/components/schemas/CallOutRecord' }
                      reject: { $ref: '#/components/schemas/Reject' }
        '400': { description: 参数非法 }
        '401': { description: 签名鉴权失败 }
        '429': { description: 限频或 24h 配额超限 }
        '500': { description: 内部错误 }
```
