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 設定 |
generate 或 plot 任務時,
都會立即扣除 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
錯誤處理
| 狀態碼 | 意義 | 常見原因 |
|---|---|---|
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_context | string | 是 | 方法論文字或論文摘錄 |
caption | string | 是 | 圖表標題,例如 Figure 1: Transformer encoder architecture |
iterations | integer | 否 | 優化迭代次數,範圍 1 到 10,預設值為 3 |
webhook_url | string | 否 | 可選回呼位址,用來接收最終結果 |
webhook_include_image | boolean | 否 | 若為 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_json | string | 是 | 包含繪圖資料的 JSON 字串 |
intent | string | 是 | 圖表類型、座標軸與風格需求的描述 |
iterations | integer | 否 | 優化迭代次數,範圍 1 到 10,預設值為 3 |
webhook_url | string | 否 | 可選回呼位址,用來接收最終結果 |
webhook_include_image | boolean | 否 | 若為 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,在任務完成或失敗時接收 HTTP POST 回呼。
設定層級
| 層級 | 設定方式 | 優先順序 |
|---|---|---|
| API Key 預設值 | 建立或更新 API Key 時設定 webhook_url 與可選的 webhook_secret |
當請求主體未指定 webhook URL 時使用 |
| 單次請求覆蓋 | 在 /api/generate 或 /api/plot 的請求主體中傳入 webhook_url |
覆蓋該次任務的預設設定 |
generate / plot 請求中的 webhook 欄位
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
webhook_url | string | 否 | 任務結束後接收結果的回呼位址 |
webhook_include_image | boolean | 否 | 若為 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_image 為 true 時,
image_base64 才會填入。否則請改用 image_url 並攜帶原始 Bearer 權杖下載圖片。
簽名驗證
若 API Key 設定了 webhook_secret,回呼請求會帶上 X-PaperBanana-Signature。
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)
);
}
重試策略
| 嘗試 | 下次重試前延遲 |
|---|---|
| 第 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_id | string | 由 generate 或 plot 回傳的任務 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-Type 為 image/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,可附帶名稱與可選配額。需要管理員驗證。
請求主體
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
name | string | 是 | 可讀名稱,例如 research-team 或 my-app |
quota | integer | 否 | 初始配額。不傳表示無限 |
webhook_url | string | 否 | 此 Key 對應任務的預設 webhook URL |
webhook_secret | string | 否 | 用於 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"
}
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 的完整資訊,以及最近的使用紀錄。需要管理員驗證。
路徑參數
| 參數 | 類型 | 說明 |
|---|---|---|
key | string | 完整 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_remaining | integer | 否 | 設定剩餘配額,最小值為 0 |
quota_total | integer | 否 | 設定總配額,最小值為 0 |
active | boolean | 否 | 啟用或停用此 Key |
webhook_url | string | 否 | 更新預設 webhook URL,傳空字串表示移除 |
webhook_secret | string | 否 | 更新 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