REST API

PaperBanana API 文档

通过异步任务式 API 生成出版级方法论图和统计图,默认文档语言可一键切换。

概述

PaperBanana 是一个用于生成出版级学术插图的 AI API, 当前支持两类工作流:

  • 方法论图表,适用于架构图、流程图、研究工作流图
  • 统计图表,适用于柱状图、折线图、直方图、散点图等

API 使用异步任务工作流:先提交请求拿到 task_id, 再轮询任务状态直到结果完成。大多数任务会在 30 到 120 秒内完成,具体取决于复杂度和迭代次数。

基础 URL

https://api.paperbanana.me

本文档中的所有接口路径都基于这个基础 URL。

身份认证

所有生成接口和管理接口都需要在 Authorization 请求头中携带 Bearer 令牌:

Authorization: Bearer pb_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
令牌类型访问范围获取方式
API Key /api/generate/api/plot/api/tasks/* 由管理员通过 POST /api/admin/keys 或管理面板创建
Admin Key /api/admin/* 通过环境变量 PAPERBANANA_ADMIN_KEY 配置
配额模型 每个 API Key 都有独立配额。每次提交 generateplot 任务时会立即扣除 1 个配额单位。

典型工作流程

标准接入通常包含三步:

1. POST /api/generate   (或 /api/plot)    ->  { task_id, status: "pending" }
2. GET  /api/tasks/{task_id}              ->  { status: "running" }
3. GET  /api/tasks/{task_id}              ->  { status: "completed", image_base64: "...", description: "..." }

可选:
GET /api/tasks/{task_id}/image            ->  直接下载 PNG
轮询建议 建议每 3 到 5 秒轮询一次。如果你不想轮询,可以改用 webhook 回调。

错误处理

状态码含义常见原因
401未授权缺少或无效的 API Key / Admin Key
403禁止访问API Key 被禁用或配额已耗尽
404未找到任务 ID 不存在、无权限访问,或图片尚不可用
422参数校验失败缺少必填字段,或 data_json 不是合法 JSON 字符串
500服务器错误内部配置或运行时异常

错误响应统一返回 JSON:

{
  "detail": "可读的错误信息"
}

健康检查

GET/api/health

用于确认 API 服务是否在线。这个接口不需要认证。

响应 200

{
  "status": "ok",
  "version": "0.1.2"
}
curl https://api.paperbanana.me/api/health
const res = await fetch("https://api.paperbanana.me/api/health");
const data = await res.json();
console.log(data);
import requests

r = requests.get("https://api.paperbanana.me/api/health")
print(r.json())

生成方法论图表

POST/api/generate

提交方法论文本和图标题,生成学术风格的架构图、工作流图或系统总览图。 接口会立即返回一个任务 ID。

请求体

字段类型必填说明
source_contextstring方法论文本或论文相关摘录
captionstring图表标题,例如 Figure 1: Transformer encoder architecture
iterationsinteger优化迭代次数,范围 1 到 10,默认值为 3
webhook_urlstring可选回调地址,用于接收最终结果
webhook_include_imageboolean若为 true,webhook 载荷中会包含 image_base64

响应 202 Accepted

{
  "task_id": "a1b2c3d4e5f6...",
  "status": "pending"
}
配额扣减时机 配额会在任务提交成功时立即扣减,而不是在任务完成时扣减。
curl -X POST https://api.paperbanana.me/api/generate \
  -H "Authorization: Bearer pb_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "source_context": "Our model uses a 6-layer Transformer encoder with multi-head self-attention.",
    "caption": "Figure 1: Transformer encoder architecture",
    "iterations": 3
  }'
const API_KEY = "pb_your_api_key_here";
const BASE = "https://api.paperbanana.me";

const submitRes = await fetch(`${BASE}/api/generate`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    source_context: "Our model uses a 6-layer Transformer encoder...",
    caption: "Figure 1: Transformer encoder architecture",
    iterations: 3
  })
});

const { task_id } = await submitRes.json();

while (true) {
  const res = await fetch(`${BASE}/api/tasks/${task_id}`, {
    headers: { "Authorization": `Bearer ${API_KEY}` }
  });
  const task = await res.json();
  if (task.status === "completed" || task.status === "failed") {
    console.log(task);
    break;
  }
  await new Promise(resolve => setTimeout(resolve, 5000));
}
import time
import requests

API_KEY = "pb_your_api_key_here"
BASE = "https://api.paperbanana.me"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

r = requests.post(f"{BASE}/api/generate", headers=HEADERS, json={
    "source_context": "Our model uses a 6-layer Transformer encoder...",
    "caption": "Figure 1: Transformer encoder architecture",
    "iterations": 3,
})
r.raise_for_status()
task_id = r.json()["task_id"]

while True:
    task = requests.get(f"{BASE}/api/tasks/{task_id}", headers=HEADERS).json()
    if task["status"] in {"completed", "failed"}:
        print(task)
        break
    time.sleep(5)

生成统计图表

POST/api/plot

提交结构化数据和图表意图描述,生成适用于论文的柱状图、折线图、直方图、散点图等图表。

请求体

字段类型必填说明
data_jsonstring包含绘图数据的 JSON 字符串
intentstring对图表类型、坐标轴和风格要求的描述
iterationsinteger优化迭代次数,范围 1 到 10,默认值为 3
webhook_urlstring可选回调地址,用于接收最终结果
webhook_include_imageboolean若为 true,webhook 载荷中会包含 image_base64

响应 202 Accepted

{
  "task_id": "f7e8d9c0b1a2...",
  "status": "pending"
}
关于 data_json 这个字段必须是JSON 字符串,而不是嵌套对象本身。 Python 请先使用 json.dumps(data),JavaScript 请先使用 JSON.stringify(data)
curl -X POST https://api.paperbanana.me/api/plot \
  -H "Authorization: Bearer pb_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "data_json": "{\"models\":[\"ResNet-50\",\"ViT-B/16\",\"EfficientNet-B4\"],\"accuracy\":[76.1,81.8,82.9]}",
    "intent": "Bar chart comparing model accuracy with value labels",
    "iterations": 3
  }'
const data = {
  models: ["ResNet-50", "ViT-B/16", "EfficientNet-B4"],
  accuracy: [76.1, 81.8, 82.9]
};

const res = await fetch("https://api.paperbanana.me/api/plot", {
  method: "POST",
  headers: {
    "Authorization": "Bearer pb_your_api_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    data_json: JSON.stringify(data),
    intent: "Bar chart comparing model accuracy with value labels",
    iterations: 3
  })
});

console.log(await res.json());
import json
import requests

data = {
    "models": ["ResNet-50", "ViT-B/16", "EfficientNet-B4"],
    "accuracy": [76.1, 81.8, 82.9],
}

r = requests.post("https://api.paperbanana.me/api/plot", headers=HEADERS, json={
    "data_json": json.dumps(data),
    "intent": "Bar chart comparing model accuracy with value labels",
    "iterations": 3,
})
print(r.json())

Webhook 回调

如果你不想轮询任务状态,可以提供 webhook_url, 当任务成功完成或失败时,PaperBanana 会主动发送 HTTP POST 回调。

配置层级

Webhook 支持两个层级:

层级配置方式优先级
API Key 默认配置 创建或更新 API Key 时设置 webhook_url 和可选的 webhook_secret 当请求体未指定 webhook URL 时使用
单次请求覆盖 /api/generate/api/plot 请求体中传入 webhook_url 覆盖该次任务的默认配置

generate / plot 请求中的 webhook 字段

字段类型必填说明
webhook_urlstring任务结束后接收结果的回调地址
webhook_include_imageboolean若为 true,回调载荷中包含 image_base64

Webhook 载荷

POST https://your-server.com/paperbanana
Content-Type: application/json
X-PaperBanana-Event: task.completed
X-PaperBanana-Signature: sha256=a1b2c3d4...
User-Agent: PaperBanana-Webhook/0.1.2

{
  "event": "task.completed",
  "task_id": "a1b2c3d4e5f6...",
  "status": "completed",
  "diagram_type": "methodology",
  "description": "The diagram shows...",
  "image_url": "https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6/image",
  "image_base64": null,
  "error": null,
  "created_at": "2026-02-15T10:30:00+00:00",
  "completed_at": "2026-02-15T10:31:25+00:00"
}
图像返回方式 只有当 webhook_include_imagetrue 时, image_base64 才会被填充。否则请使用 image_url 并携带原始 Bearer 令牌下载图片。

签名校验

如果该 API Key 配置了 webhook_secret, 回调请求会带上 X-PaperBanana-Signature, 它是对原始请求体做 HMAC-SHA256 签名后的结果。

import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
const crypto = require("crypto");

function verifySignature(body, signature, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

重试策略

如果因为网络错误或 HTTP 状态码大于等于 400 导致投递失败,PaperBanana 最多会重试三次:

尝试下一次重试前的延迟
第 1 次重试5 秒
第 2 次重试15 秒
第 3 次重试60 秒

你可以在管理界面的历史记录中查看 webhook 投递状态。

带 webhook 的请求示例

curl -X POST https://api.paperbanana.me/api/generate \
  -H "Authorization: Bearer pb_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "source_context": "Our model uses a 6-layer Transformer encoder...",
    "caption": "Figure 1: Transformer architecture",
    "iterations": 3,
    "webhook_url": "https://your-server.com/pb-callback",
    "webhook_include_image": false
  }'
import requests

r = requests.post("https://api.paperbanana.me/api/generate", headers=HEADERS, json={
    "source_context": "Our model uses a 6-layer Transformer encoder...",
    "caption": "Figure 1: Transformer architecture",
    "iterations": 3,
    "webhook_url": "https://your-server.com/pb-callback",
    "webhook_include_image": False,
})
print(r.json())
const res = await fetch("https://api.paperbanana.me/api/generate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer pb_your_api_key_here",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    source_context: "Our model uses a 6-layer Transformer encoder...",
    caption: "Figure 1: Transformer architecture",
    iterations: 3,
    webhook_url: "https://your-server.com/pb-callback",
    webhook_include_image: false
  })
});
console.log(await res.json());

查询任务状态与结果

GET/api/tasks/{task_id}

获取任务当前状态。任务完成后,响应中会包含 base64 编码图片和生成描述。

路径参数

参数类型说明
task_idstringgenerateplot 返回的任务 ID

响应 200

{
  "task_id": "a1b2c3d4e5f6...",
  "status": "completed",
  "diagram_type": "methodology",
  "image_base64": "iVBORw0KGgo...",
  "description": "The diagram shows...",
  "error": null
}

状态生命周期

状态含义下一步
pending任务已进入队列继续轮询
running正在生成中继续轮询
completed结果已完成读取 image_base64 或调用图片下载接口
failed任务失败检查 error 字段
curl https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6 \
  -H "Authorization: Bearer pb_your_api_key_here"
const res = await fetch("https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6", {
  headers: { "Authorization": "Bearer pb_your_api_key_here" }
});
const task = await res.json();
console.log(task);
r = requests.get(
    "https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6",
    headers=HEADERS
)
task = r.json()
print(task)

下载任务图片

GET/api/tasks/{task_id}/image

直接下载最终 PNG。只有任务状态为 completed 时才可用。

响应 200

返回二进制 PNG,响应头中的 Content-Typeimage/png

响应 404

任务不存在,或图片当前尚不可用。

curl -o diagram.png https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6/image \
  -H "Authorization: Bearer pb_your_api_key_here"
const res = await fetch("https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6/image", {
  headers: { "Authorization": "Bearer pb_your_api_key_here" }
});
const blob = await res.blob();
const url = URL.createObjectURL(blob);
window.open(url, "_blank");
r = requests.get(
    "https://api.paperbanana.me/api/tasks/a1b2c3d4e5f6/image",
    headers=HEADERS
)
r.raise_for_status()
with open("diagram.png", "wb") as f:
    f.write(r.content)

创建 API Key

POST/api/admin/keys

创建一个新的 API Key,可附带名称和可选配额。需要管理员认证。

请求体

字段类型必填说明
namestring可读名称,例如 research-teammy-app
quotainteger初始配额。不传则表示无限
webhook_urlstring此 Key 对应任务的默认 webhook URL
webhook_secretstring用于 webhook HMAC-SHA256 签名的密钥

响应 201 Created

{
  "key": "pb_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "name": "my-app",
  "quota_remaining": 100,
  "quota_total": 100,
  "total_used": 0,
  "active": true,
  "created_at": "2026-02-15T10:30:00+00:00",
  "webhook_url": "https://your-server.com/callback"
}
仅返回一次 完整 API Key 只会在创建响应中返回一次,请在创建后立即保存。
curl -X POST https://api.paperbanana.me/api/admin/keys \
  -H "Authorization: Bearer YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "quota": 100}'
const res = await fetch("https://api.paperbanana.me/api/admin/keys", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_ADMIN_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ name: "my-app", quota: 100 })
});
console.log(await res.json());
ADMIN_HEADERS = {"Authorization": "Bearer YOUR_ADMIN_KEY"}

r = requests.post("https://api.paperbanana.me/api/admin/keys",
    headers=ADMIN_HEADERS,
    json={"name": "my-app", "quota": 100}
)
print(r.json())

API Key 列表

GET/api/admin/keys

返回所有 API Key 及其配额和使用情况。需要管理员认证。

响应 200

[
  {
    "key": "pb_a1b2c3...",
    "name": "my-app",
    "quota_remaining": 87,
    "quota_total": 100,
    "total_used": 13,
    "active": true,
    "created_at": "2026-02-15T10:30:00+00:00"
  }
]
curl https://api.paperbanana.me/api/admin/keys \
  -H "Authorization: Bearer YOUR_ADMIN_KEY"
const res = await fetch("https://api.paperbanana.me/api/admin/keys", {
  headers: { "Authorization": "Bearer YOUR_ADMIN_KEY" }
});
console.log(await res.json());
r = requests.get("https://api.paperbanana.me/api/admin/keys", headers=ADMIN_HEADERS)
print(r.json())

API Key 详情

GET/api/admin/keys/{key}

返回 API Key 的完整信息,以及最近的使用日志。需要管理员认证。

路径参数

参数类型说明
keystring完整 API Key 字符串

响应 200

{
  "key": "pb_a1b2c3...",
  "name": "my-app",
  "quota_remaining": 87,
  "quota_total": 100,
  "total_used": 13,
  "active": true,
  "created_at": "2026-02-15T10:30:00+00:00",
  "usage_log": [
    {
      "id": 1,
      "task_id": "abc123...",
      "endpoint": "generate",
      "created_at": "2026-02-15T11:00:00+00:00"
    }
  ]
}
curl https://api.paperbanana.me/api/admin/keys/pb_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 \
  -H "Authorization: Bearer YOUR_ADMIN_KEY"
api_key = "pb_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
r = requests.get(f"https://api.paperbanana.me/api/admin/keys/{api_key}", headers=ADMIN_HEADERS)
print(r.json())

更新 API Key

PATCH/api/admin/keys/{key}

更新配额、启用状态或 webhook 默认配置。请求体中的所有字段都是可选的。

请求体

字段类型必填说明
quota_remaininginteger设置剩余配额,最小为 0
quota_totalinteger设置总配额,最小为 0
activeboolean启用或禁用该 Key
webhook_urlstring更新默认 webhook URL,传空字符串表示移除
webhook_secretstring更新 webhook 签名密钥,传空字符串表示移除

响应 200

接口会返回更新后的 API Key 对象。

curl -X PATCH https://api.paperbanana.me/api/admin/keys/pb_a1b2c3... \
  -H "Authorization: Bearer YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"quota_remaining": 150, "quota_total": 200, "active": true}'
r = requests.patch(
    "https://api.paperbanana.me/api/admin/keys/pb_a1b2c3...",
    headers=ADMIN_HEADERS,
    json={"quota_remaining": 150, "quota_total": 200, "active": True}
)
print(r.json())

删除 API Key

DELETE/api/admin/keys/{key}

永久删除一个 API Key 及其使用日志。该操作不可撤销。

响应 204 No Content

删除成功时响应体为空。

curl -X DELETE https://api.paperbanana.me/api/admin/keys/pb_a1b2c3... \
  -H "Authorization: Bearer YOUR_ADMIN_KEY"
r = requests.delete(
    "https://api.paperbanana.me/api/admin/keys/pb_a1b2c3...",
    headers=ADMIN_HEADERS
)
assert r.status_code == 204