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에는 개별 쿼터가 있습니다. 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 직접 다운로드
폴링 간격 3~5초 간격으로 상태를 확인하는 것을 권장합니다. 폴링을 원하지 않으면 webhook을 사용할 수 있습니다.

오류 처리

상태 코드의미주요 원인
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_contextstring방법론 텍스트 또는 논문 발췌문
captionstring그림 제목. 예: Figure 1: Transformer encoder architecture
iterationsinteger아니오정제 반복 횟수. 1~10, 기본값 3
webhook_urlstring아니오최종 결과를 받을 콜백 URL
webhook_include_imageboolean아니오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_jsonstring플로팅에 사용할 데이터가 들어 있는 JSON 문자열
intentstring차트 유형, 축, 스타일 요구사항 설명
iterationsinteger아니오정제 반복 횟수. 1~10, 기본값 3
webhook_urlstring아니오최종 결과를 받을 콜백 URL
webhook_include_imageboolean아니오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_urlstring아니오작업 종료 후 결과를 받을 콜백 URL
webhook_include_imageboolean아니오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_imagetrue일 때만 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)
  );
}

재시도 정책

시도다음 재시도 전 대기 시간
재시도 15초
재시도 215초
재시도 360초

웹훅 전송 상태는 관리자 화면의 히스토리에서 확인할 수 있습니다.

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_idstringgenerate 또는 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를 생성합니다. 관리자 인증이 필요합니다.

요청 본문

필드타입필수설명
namestringresearch-team, my-app 같은 사람이 읽기 쉬운 이름
quotainteger아니오초기 쿼터. 생략하면 무제한
webhook_urlstring아니오이 Key로 생성한 작업의 기본 webhook URL
webhook_secretstring아니오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"
}
한 번만 표시됨 전체 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