MoonAPI 接口文档
自建存储(Cloudflare R2 + KV)驱动的 API 服务。当前提供「每日知识卡片」对外接口,以及配套的后台管理接口。
Base URL 与认证
| 项 | 值 |
|---|---|
| Base URL | https://api.lunor.top |
| 认证方式 | 请求头 Authorization: Bearer <API_KEY>(或 X-API-Key: <API_KEY>) |
| 密钥获取 | 登录 管理后台 → 「API 密钥」页创建(形如 mk_live_…) |
| 数据格式 | JSON(UTF-8),全部接口允许跨域(CORS) |
| 日期口径 | 「每日」按北京时间(UTC+8)自然日计算 |
每日知识卡片对外 · 需密钥
GET/api/v1/daily-card
返回当日知识卡片。同一天内多次请求返回同一张卡片;日期到达后自动轮换。
查询参数
| 参数 | 必填 | 说明 |
|---|---|---|
| date | 否 | 指定日期,格式 YYYY-MM-DD;缺省为今天 |
成功响应 200
{
"code": 0,
"message": "success",
"requestId": "8f2c…",
"data": {
"date": "2026-09-01",
"source": "auto", // pinned=后台排期 | auto=自动轮换 | builtin=内置兜底
"card": {
"id": "6f9c…",
"title": "月亮正以每年约 3.8 厘米的速度远离地球",
"category": "科学",
"content": "由于潮汐相互作用……",
"source": "NASA 月球测距计划",
"tags": ["天文", "月球"],
"createdAt": "2026-09-01T05:00:00.000Z",
"updatedAt": "2026-09-01T05:00:00.000Z"
}
}
}
管理后台可为指定日期「排期」固定卡片;未排期的日期按日期哈希在卡片库中自动轮换;卡片库为空时返回内置卡片,接口永不为空。该接口同时接受管理员令牌(后台「试调用」页可直接联调),管理员测试不计入密钥配额。
代理转发对外 · 需密钥
GET/api/v1/proxy/{slug}
把请求转发到后台配置的第三方上游接口(如 api.yujin.cn),JSON 结果包装为 MoonAPI 统一信封(二创转发);二进制结果(图片等)直接透传流。
说明
| 项 | 值 |
|---|---|
| 可用服务 | 在 管理后台 →「代理服务」中查看与配置,无需重新部署 |
| 查询参数 | 调用方携带的查询参数会原样透传给上游 |
| 管理员调试 | 该接口同时接受管理员令牌,便于后台「测试」按钮直接联调 |
| 缓存 | 按服务配置的秒数在 KV 中缓存 JSON 响应(命中时 message: success (cache)) |
成功响应 200(JSON 上游)
{
"code": 0,
"message": "success",
"requestId": "…",
"data": {
"slug": "yujin-root",
"name": "雨瑾云 · 站点首页",
"source": "api.yujin.cn",
"fetchedAt": "2026-09-01T12:00:00.000Z",
"upstreamStatus": 200,
"upstream": { /* 上游原始 JSON */ }
}
}
相关错误
| code | HTTP | 含义 |
|---|---|---|
| 40403 | 404 | slug 对应的代理服务不存在 |
| 40303 | 403 | 代理服务已被管理员停用 |
| 50201 / 50401 | 502 / 504 | 上游请求失败 / 超时 |
媒体文件对外 · 公开
GET/api/v1/media/{id}
分发存储在 R2 中的媒体文件(图片等)。文件由管理后台「媒体库」上传,链接形如
https://api.lunor.top/api/v1/media/<uuid>,可直接用于
<img src>。响应带 24 小时浏览器缓存头。
健康检查公开
GET/api/health
无需认证。返回服务状态及 KV / R2 绑定自检结果(bindings.kv、bindings.r2 均为 true 表示后端存储配置正常)。
管理接口管理员令牌
认证与端点清单
管理接口供后台页面使用,需请求头 Authorization: Bearer <管理员令牌>。令牌在首次访问 /admin 时初始化(仅存哈希)。/api/admin/setup 只能成功调用一次。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/admin/setup | 初始化管理员令牌,请求体 {"token":"…"}(≥8 位) |
| POST | /api/admin/login | 校验管理员令牌 |
| GET | /api/admin/cards | 卡片列表(索引)。首次使用时自动分批导入内置 342 条资料库(打开后台卡片页即自动完成;完成后每日卡片跨天自动轮换) |
| POST | /api/admin/cards | 新增卡片(单个对象或 {"cards":[…]} 批量,≤500 条) |
| GET PUT DELETE | /api/admin/cards/{id} | 读取 / 更新 / 删除单张卡片 |
| POST | /api/admin/cards/batch-delete | 批量删除,请求体 {"ids":[…]} |
| GET | /api/admin/daily | 排期列表 |
| POST | /api/admin/daily | 设置排期 {"date":"YYYY-MM-DD","cardId":"…"};cardId 为 null 表示清除 |
| GET | /api/admin/keys | API 密钥列表(含每日配额) |
| POST | /api/admin/keys | 创建密钥 {"name":"备注","dailyQuota":0}(0=不限);吊销 {"action":"revoke","key":"…"} |
| GET | /api/admin/proxy | 代理服务列表 |
| POST | /api/admin/proxy | 新增/更新代理服务 {slug,name,url,method,cacheTtl,timeoutMs,enabled};删除 {"action":"delete","slug":"…"} |
| GET | /api/admin/media | 媒体文件列表 |
| POST | /api/admin/media | 上传 {name,contentType,base64}(≤5MB);删除 {"action":"delete","id":"…"} |
| GET | /api/admin/stats?days=7 | 用量统计(近 N 天调用量、按键/接口分布、资源汇总) |
| GET | /api/admin/logs?days=7&limit=100 | 调用日志列表(支持 route / slug / status / requestId 过滤),附 success 汇总;?key= 查询单条明细 |
| POST | /api/admin/logs | 清理调用日志 {"action":"clear","keepDays":30}(keepDays=0 清空;单次最多处理 10 天) |
每次对外调用都会在 R2 中写入一条调用明细(含服务端分阶段计时),保留 30 天后自动清理;敏感请求参数(key / token / password 等)写入前自动掩码。
设置过
dailyQuota 的密钥,在每日卡片与代理接口上按北京时间自然日限流,超限返回 42901(HTTP 429)。卡片字段
| 字段 | 必填 | 说明 |
|---|---|---|
| title | 是 | 标题,≤120 字 |
| content | 是 | 正文,≤5000 字 |
| category | 否 | 分类,缺省「未分类」 |
| source | 否 | 内容来源 |
| tags | 否 | 字符串数组,≤10 个 |
响应信封
所有接口返回统一结构:{code, message, data, requestId}。code=0 表示成功,非 0 时 HTTP 状态码同步为非 2xx,message 为可直接展示的错误说明。
错误码表
| code | HTTP | 含义 |
|---|---|---|
| 40000 | 400 | 请求体不是合法 JSON |
| 40001 | 400 | date 参数格式错误 |
| 40002 | 400 | 管理员令牌长度不足 |
| 40003 / 40005 | 400 | 批量数量超出 1-500 限制 |
| 40004 / 40006 | 400 | 更新内容无效 / 缺少待吊销密钥 |
| 40101 / 40102 | 401 | 缺少管理员令牌 / 令牌错误 |
| 40103 / 40104 | 401 | 缺少 API Key / Key 无效 |
| 40302 | 403 | API Key 已被吊销 |
| 40401 | 404 | 卡片不存在 |
| 40901 | 409 | 管理员已初始化,禁止重复 setup |
| 40303 | 403 | 代理服务已停用 |
| 40402 / 40403 / 40404 | 404 | 密钥不存在 / 代理服务不存在 / 媒体文件不存在 |
| 40007–40013 | 400 | slug 非法 / 上游地址非法 / method 不支持 / 上传内容缺失或非法 / 清理参数非法(40012)等 |
| 41301 | 413 | 媒体文件超过 5MB 上限 |
| 42901 | 429 | API Key 已达每日配额 |
| 50201 / 50202 / 50401 | 502 | 上游请求失败 / 上游返回非预期内容(诊断信息随响应返回) / 上游超时 |
| 50301 | 503 | 管理员未初始化,需先调用 setup |
| 40405 | 404 | 调用日志条目不存在或已被清理 |
调用示例
cURL
curl -s https://api.lunor.top/api/v1/daily-card \ -H "Authorization: Bearer mk_live_xxxxxxxx"
JavaScript(fetch)
const res = await fetch('https://api.lunor.top/api/v1/daily-card', {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const { code, data } = await res.json();
if (code === 0) {
console.log(data.date, data.card.title, data.card.content);
}
Python(requests)
import requests
r = requests.get(
"https://api.lunor.top/api/v1/daily-card",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=10,
)
r.raise_for_status()
card = r.json()["data"]["card"]
print(card["title"], card["content"], sep="\n")