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 형식 오류 |
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
방법론 텍스트와 그림 캡션을 보내 학술 스타일의 아키텍처 다이어그램이나 워크플로 다이어그램을 생성합니다.
요청 본문
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
source_context | string | 예 | 방법론 텍스트 또는 논문 발췌문 |
caption | string | 예 | 그림 제목. 예: Figure 1: Transformer encoder architecture |
iterations | integer | 아니오 | 정제 반복 횟수. 1~10, 기본값 3 |
webhook_url | string | 아니오 | 최종 결과를 받을 콜백 URL |
webhook_include_image | boolean | 아니오 | true이면 webhook payload에 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 | 아니오 | 최종 결과를 받을 콜백 URL |
webhook_include_image | boolean | 아니오 | true이면 webhook payload에 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_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 | 아니오 | 작업 종료 후 결과를 받을 콜백 URL |
webhook_include_image | boolean | 아니오 | true이면 payload에 image_base64 포함 |
Webhook Payload
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가 채워집니다.
그렇지 않으면 원래 Bearer 토큰으로 image_url을 호출해 PNG를 내려받아야 합니다.
서명 검증
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 예시 요청
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
Content-Type: image/png인 바이너리 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 payload 서명용 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