API 참조
이 문서는 api/app.js 내 Express 앱의 동작과 api/routes/ 아래의 라우트 핸들러와 일치합니다.
제한 및 동작
| 항목 | 값 |
|---|---|
| JSON 본문 크기 | 최대 2 MB (express.json({ limit: '2mb' })) |
| 요청당 대상 | 1–36 개의 언어 코드 |
| 배치 항목 | 배치 요청당 1–100 항목 |
| 모델 | standard (기본) 또는 advanced (유료 등급 전용; 아래 참조) |
월별 토큰 허용량 (무료 등급): 모델 호출 전에 API는 토큰을 대략 ceil(content_length / 4) × (number_of_targets + 1)로 추정하며, free 등급에 한해 추정치가 남은 월별 할당량(FREE_TIER_MONTHLY_TOKENS, 기본값 100000)을 초과하면 429 / token_limit_reached로 요청을 거부합니다. 유료 등급은 enforceTokenCap에서 이 사전 검사를 차단하지 않으며, 사용량은 계속 기록됩니다.
요청 제한: Upstash Redis가 구성된 경우(UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN, URL에 your-instance 자리 표시자가 없을 때) 등급별 분당 제한이 적용됩니다: 무료 5, 스타터 30, 성장 60, 스케일 120, 엔터프라이즈 무제한. 제한 초과 시 응답은 429 및 error: "rate_limit_reached"입니다. Redis가 구성되지 않은 경우 요청 제한은 건너뜁니다(rateLimit.js 참조).
성공적인 요청 제한 응답에는 X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset이 포함될 수 있습니다.
GET /health
인증 없음.
응답 200
{
"status": "ok",
"timestamp": "2025-03-23T12:00:00.000Z"
}
GET /languages
인증 없음.
지원되는 언어의 표준 목록(코드, 표시 이름, RTL 플래그)을 반환합니다. 총 36개 항목이 있으며, targets에서 허용되는 값은 코드뿐입니다.
응답 200
{
"languages": [
{ "code": "en", "name": "English", "rtl": false },
{ "code": "ar", "name": "Arabic", "rtl": true }
]
}
POST /translate
Authorization: Bearer <api_key> 필요.
단일 content 문자열을 targets에 나열된 모든 언어로 번역합니다. 모델은 요청된 언어 코드와 정확히 일치하는 키를 가진 단일 JSON 객체를 반환하며, 값은 번역된 문자열입니다(formatPrompts.js 참조).
요청 본문
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
content | string | 예 | 번역할 비어 있지 않은 문자열 |
targets | string[] | 예 | 유효한 언어 코드의 비어 있지 않은 배열 (최대 36) |
format | string | 아니오 | plain, markdown, json, html 중 하나. 생략 시 content에서 자동 감지 |
source | string | 아니오 | 모델에 대한 원본 언어 힌트; 선택 사항 |
model | string | 아니오 | standard (기본) 또는 advanced. advanced는 유료 등급 필요 (무료 등급은 403) |
응답 200
{
"translations": {
"es": "...",
"fr": "..."
},
"usage": {
"input_tokens": 120,
"output_tokens": 340,
"total_tokens": 460,
"model": "standard",
"detected_format": "markdown",
"detection_confidence": 0.95
}
}
detected_format 및 detection_confidence는 format이 생략되어 자동 감지가 실행된 경우에만 나타납니다.
예시 (cURL)
curl -sS -X POST "https://api.usepolylingo.com/v1/translate" \
-H "Authorization: Bearer $POLYLINGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "{\"title\":\"Hello\"}",
"format": "json",
"targets": ["fr", "de"]
}'
예시 (Python 3)
pip install requests
import os, requests
url = "https://api.usepolylingo.com/v1/translate"
headers = {
"Authorization": f"Bearer {os.environ['POLYLINGO_API_KEY']}",
"Content-Type": "application/json",
}
r = requests.post(url, json={
"content": "<p>Hello <strong>world</strong></p>",
"format": "html",
"targets": ["es"],
}, timeout=120)
r.raise_for_status()
print(r.json()["translations"]["es"])
POST /translate/batch
Authorization: Bearer <api_key> 필요.
각 항목을 순차적으로 처리합니다(항목당 모델 호출 1회). 항목 중 하나라도 실패하면 API는 500을 반환하며 해당 요청에 대한 부분 결과를 반환하지 않습니다.
요청 본문
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
items | 배열 | 예 | 각 요소: id (문자열), content (문자열), 선택적 format |
targets | string[] | 예 | /translate와 동일한 규칙 |
source | string | 아니오 | 선택적 원본 언어 힌트 |
model | string | 아니오 | standard 또는 advanced (/translate와 동일한 규칙) |
응답 200
{
"results": [
{ "id": "welcome", "translations": { "fr": "...", "de": "..." } },
{ "id": "goodbye", "translations": { "fr": "...", "de": "..." } }
],
"usage": {
"total_tokens": 900,
"input_tokens": 400,
"output_tokens": 500,
"model": "standard"
}
}
예시
curl -sS -X POST "https://api.usepolylingo.com/v1/translate/batch" \
-H "Authorization: Bearer $POLYLINGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "a", "content": "Hello", "format": "plain" },
{ "id": "b", "content": "## Title", "format": "markdown" }
],
"targets": ["es", "it"]
}'
POST /jobs
Authorization: Bearer <api_key> 필요.
번역 작업을 큐에 넣고 즉시 job_id를 반환합니다. 번역은 백그라운드에서 실행되므로 콘텐츠 크기와 관계없이 HTTP 타임아웃 위험이 없습니다. 결과는 GET /jobs/:id로 폴링하세요.
긴 문서(긴 Markdown, 많은 대상 언어)를 번역할 때 요청 시간이 HTTP 클라이언트 또는 프록시 타임아웃을 초과할 수 있으므로 이 엔드포인트를 POST /translate 대신 사용하세요.
요청 본문
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
content | string | 예 | 번역할 비어 있지 않은 문자열 |
targets | string[] | 예 | 유효한 언어 코드의 비어 있지 않은 배열 (최대 36) |
format | string | 아니오 | plain, markdown, json, html 중 하나. 생략 시 자동 감지 |
source | string | 아니오 | 원본 언어 힌트; 선택 사항 |
model | string | 아니오 | standard (기본) 또는 advanced |
응답 202
{
"job_id": "a1b2c3d4-...",
"status": "pending",
"created_at": "2025-03-23T12:00:00.000Z"
}
GET /jobs/:id
Authorization: Bearer <api_key> 필요.
POST /jobs로 제출된 작업의 상태를 폴링합니다. 5~10초마다 폴링하세요. 작업은 제출한 사용자 소유이며, 다른 사용자는 404를 받습니다.
응답 (대기 중 / 처리 중)
{
"job_id": "a1b2c3d4-...",
"status": "pending",
"created_at": "2025-03-23T12:00:00.000Z",
"updated_at": "2025-03-23T12:00:00.000Z",
"completed_at": null,
"queue_position": 3
}
status는 pending (작업자 대기 중) 또는 processing (작업자가 작업을 가져감)입니다. queue_position (1부터 시작)은 이 작업보다 엄격히 먼저 생성된 대기 중 또는 처리 중 작업 수이며 진행 UI에 사용하세요. 쿼리 실패 시 생략됩니다.
응답 (완료됨)
{
"job_id": "a1b2c3d4-...",
"status": "completed",
"created_at": "2025-03-23T12:00:00.000Z",
"updated_at": "2025-03-23T12:00:02.000Z",
"completed_at": "2025-03-23T12:00:02.000Z",
"translations": {
"es": "...",
"fr": "..."
},
"usage": {
"input_tokens": 120,
"output_tokens": 340,
"total_tokens": 460,
"model": "standard"
}
}
응답 (실패)
{
"job_id": "a1b2c3d4-...",
"status": "failed",
"error": "Model returned invalid JSON"
}
예시 (JavaScript)
const API = 'https://api.usepolylingo.com/v1'
const headers = {
'Authorization': `Bearer ${process.env.POLYLINGO_API_KEY}`,
'Content-Type': 'application/json',
}
// 1. 제출
const submit = await fetch(`${API}/jobs`, {
method: 'POST',
headers,
body: JSON.stringify({ content: longMarkdown, format: 'markdown', targets: ['de', 'fr'] }),
})
const { job_id } = await submit.json()
// 2. 폴링
while (true) {
await new Promise(r => setTimeout(r, 10_000))
const poll = await fetch(`${API}/jobs/${job_id}`, { headers })
const job = await poll.json()
if (job.status === 'completed') { console.log(job.translations); break }
if (job.status === 'failed') { throw new Error(job.error) }
// 선택 사항: 진행 상황 표시 (queue_position은 1부터 시작하며, 큐에 없으면 생략)
if (job.queue_position != null) console.log(`Queue position: ${job.queue_position}`)
}
GET /usage
Authorization: Bearer <api_key> 필요 (표준 키 조회 — 내부 우회 전용 경로 아님).
인증된 사용자의 현재 달력 월 토큰 사용량을 반환합니다.
응답 200
{
"period_start": "2025-03-01T00:00:00.000Z",
"period_end": "2025-03-31T23:59:59.000Z",
"tokens_used": 12000,
"tokens_included": 100000,
"tokens_remaining": 88000,
"overage_tokens": 0,
"tier": "free"
}
tokens_included 및 tokens_remaining은 enterprise의 경우 null입니다 (보고서에 무제한 할당).
콘텐츠 형식
지원되는 format 값: plain, markdown, json, html.
| 형식 | 보존됨 | 번역됨 |
|---|---|---|
plain | 줄 바꿈 / 단락 | 모든 가시 텍스트 |
markdown | 구문, 링크(URL 변경 없음), 펜스 코드(있는 그대로) | 산문 및 링크 텍스트 |
json | 키, 구조, 비문자열 유형 | 문자열 값만 |
html | 태그 및 속성 | 텍스트 노드 및 적절한 속성(프롬프트 참조) |
앱에서의 RTL 및 방향
plain 및 markdown 출력의 경우, API는 번역된 텍스트만 반환하며 dir="rtl" 또는 래퍼 요소를 추가하지 않습니다. 아랍어, 히브리어 또는 페르시아어를 표시할 때 UI에서 텍스트 방향(CSS direction, 상위 요소의 dir 속성 또는 프레임워크의 i18n 레이아웃)을 설정하세요.
html 형식의 경우, 번역된 마크업에 RTL 대상에 적합한 경우 dir="rtl"이 포함될 수 있습니다; formatPrompts.js 및 scripts/test-translation.js의 HTML 테스트 참조.
오류 응답
오류는 가능하면 JSON 형식입니다:
{
"error": "invalid_request",
"message": "사람이 읽을 수 있는 상세 내용"
}
| HTTP | error | 상황 |
|---|---|---|
| 400 | invalid_request | 본문 필드 누락/잘못됨 (예: 빈 content, 잘못된 targets) |
| 400 | invalid_format | 지원되지 않는 format |
| 400 | invalid_language | targets에 알 수 없는 코드 포함 |
| 401 | invalid_api_key | 누락/잘못된 Authorization, 알 수 없는 키, 취소된 키 |
| 403 | advanced_not_available | 무료 등급에서 model: "advanced" 요청 |
| 429 | token_limit_reached | 무료 등급 월별 한도 초과 (사전 검사) |
| 429 | rate_limit_reached | 분당 RPM 제한 초과 (Redis 활성화 시) |
| 500 | translation_error | 모델/네트워크 실패; 재시도 가능 |
| 404 | not_found | GET /jobs/:id — 작업이 없거나 다른 사용자 소유 |
| 500 | server_error | POST /jobs — 큐에 넣기 실패; 재시도 가능 |
GET /usage는 Supabase 쿼리 실패 시 일반 메시지와 함께 500을 반환할 수 있습니다.
기본 모델 (참고용)
API는 standard와 advanced만 노출합니다. 실제 OpenAI 모델 ID는 api/utils/modelRouter.js에서 구성되며 API 응답에는 포함되지 않습니다.