API 개요
모든 엔드포인트는 /api/v1 아래에 있다.
스펙 원본은 apps/web/src/lib/openapi.ts에 OpenAPI 3.1로 유지되고, GET /api/v1/openapi가 그 객체를 그대로 JSON으로 돌려준다. 인증이 필요 없다.
curl -s http://localhost:3000/api/v1/openapi > openapi.jsonapps/web/tests/openapi.test.ts가 app/api/v1/ 밑의 모든 라우트가 스펙에 있고 메서드까지 일치하는지 검사한다. 이 검사는 Markdown 예시나 모든 응답 필드의 정확성까지 보장하지 않으므로, 연동할 때는 배포한 서버의 스펙도 확인한다.
엔드포인트 목록
| 메서드 | 경로 | scope | 설명 |
|---|---|---|---|
| GET | /openapi | — | 이 스펙 자체 |
| GET | /health | — | DB 연결 포함 상태 확인 |
| POST | /setup | — | 최초 관리자 계정 생성 (사용자 0명일 때만) |
| POST | /auth/register | — | 계정 만들기 (누구나, 역할은 viewer 고정) |
| POST | /auth/login | — | 세션 쿠키 발급 |
| PATCH | /account | 세션 | 내 표시 이름 변경 |
| DELETE | /account | 세션 | 내 계정 탈퇴 |
| POST | /account/sso-refresh | 세션 | 내 SSO 이름·이메일·그룹 다시 가져오기 |
| POST | /auth/logout | 세션 | 세션 폐기 |
| GET | /keys | 세션 | API 키 목록 |
| POST | /keys | 세션 | API 키 발급 |
| DELETE | /keys/{id} | 세션 | API 키 폐기 |
| GET | /sso | 세션(admin) | SSO 설정 조회 (시크릿 제외) |
| PUT | /sso | 세션(admin) | SSO 설정 저장 |
| POST | /sso/discover | 세션(admin) | issuer의 발견 문서로 엔드포인트 채우기 |
| GET | /sso/proxy-check | 세션(admin) | 현재 요청에 실제 도착한 프록시 헤더 진단 |
| GET | /admin/users | 세션(admin) | 사용자·에이전트 계정 목록 |
| POST | /admin/users | 세션(admin) | API 전용 에이전트 계정과 키 생성 |
| GET | /admin/ai-config | 세션(admin) | 마스킹된 AI 연결 설정 조회 |
| PATCH | /admin/ai-config | 세션(admin) | AI 연결 설정 저장 |
| POST | /admin/ai-config/models | 세션(admin) | 공급자가 제공하는 모델 목록 조회 |
| POST | /admin/ai-config/test | 세션(admin) | 선택 모델로 실제 생성 요청 시험 |
| GET | /admin/ai-observability | 세션(admin) | AI 호출 집계·실패와 RAG·검토 큐 상태 |
| GET | /admin/rag-config | 세션(admin) | RAG·Embedding·Reranker 설정과 색인 통계 |
| PATCH | /admin/rag-config | 세션(admin) | RAG 연결·청크 설정 저장 |
| POST | /admin/rag-config/models | 세션(admin) | /v1/models에서 Embedding·Reranker 모델 선택지 조회 |
| POST | /admin/rag-config/test | 세션(admin) | Embedding·Reranker 연결 시험 |
| POST | /admin/rag-config/reindex | 세션(admin) | 전체 용어·회의록·위키 재색인 대기열 생성 |
| POST | /rag/search | read | pgvector 용어집 검색 |
| POST | /rag/meetings/search | read | 기존 회의 자료 pgvector 검색 |
| POST | /rag/wiki/search | read | 공개 위키 pgvector 검색 |
| GET | /lexicon | read | 검증용 용어 표기 스냅샷 (ETag 지원) |
| POST | /validate | validate | 문서 하나의 용어 사용 검증 |
| POST | /validate/batch | validate | 여러 문서의 용어 사용 일괄 검증 |
| GET | /candidates | read | 문서 검증에서 발견한 미등록 후보 목록 |
| POST | /candidates/{id}/dismiss | write | 미등록 후보 무시 |
| POST | /candidates/{id}/promote | write | 미등록 후보를 용어로 등록 |
| GET | /meetings | read | 기존 회의 자료 목록 |
| POST | /meetings | write | 기존 연동 호환용 회의 자료 저장 |
| GET | /meetings/{id} | read | 기존 회의 자료 원문 조회 |
| PATCH | /meetings/{id} | write | 기존 회의 자료 수정·보관 |
| GET | /wiki | read | 위키 문서 목록·검색 |
| POST | /wiki | write | 위키 문서 생성·RAG 색인 예약 |
| GET | /wiki/{slug} | read | 위키 문서 조회 |
| PATCH | /wiki/{slug} | write | 위키 문서 수정·RAG 색인 예약 |
| GET | /admin/exports/terms | 세션(admin) | 서버 전체 용어집 읽기 전용 스냅샷 다운로드 |
| GET | /admin/sync | 세션(admin) | 동기화 식별자·최근 내보내기·출처 서버별 마지막 적용 결과 |
| DELETE | /admin/sync?source={id} | 세션(admin) | 출처 서버와의 동기화 연결 해제(데이터는 유지) |
| GET | /admin/sync/export | 세션(admin) | 전체·변경분 동기화 번들(gzip) 다운로드 |
| POST | /admin/sync/import | 세션(admin) | 동기화 번들 미리보기(기본)·반영 |
| GET | /admin/menu-settings | 세션(admin) | 사이드바 메뉴 표시 설정 조회 |
| PATCH | /admin/menu-settings | 세션(admin) | 워크스페이스 전체의 부가 메뉴 표시 여부 저장 |
| GET | /admin/term-quality | 세션(admin) | 콘텐츠 완성도 조회 |
| POST | /admin/term-quality | 세션(admin) | 최소 길이 변경 영향 미리보기 |
| PATCH | /admin/term-quality | 세션(admin) | 최소 길이 저장 |
| GET | /contributions/review-queue | read | AI 작업 상태와 목록 |
| POST | /contributions/review-queue | write | AI 검토 요청 또는 대기 작업 재개 |
| GET | /contributions/suggestions | read | 현재 리비전의 AI 제안 조회 |
| PATCH | /contributions/suggestions | write | AI 관계 제안 승인·거절 |
| DELETE | /contributions/suggestions | write | AI 필드 제안 거절 |
| GET | /contributions/term-definitions | read | 한줄 정의 정리 대상과 캐시된 제안 |
| POST | /contributions/term-definitions | write | LLM 한줄 정의 생성 |
| PATCH | /contributions/term-definitions | write | 한줄 정의 한 건 승인 |
| POST | /contributions/classifications | write | 도메인·업무 분류 AI 추천 생성 |
| POST | /contributions/suggestion-dispositions | write | AI 제안 숨김·보류·개인 저장 |
| DELETE | /contributions/suggestion-dispositions | write | 내 작업에서 AI 제안 제거 |
| GET | /contributions/duplicates | read | 중복 후보 쌍·검토 상태 |
| POST | /contributions/duplicates | write | AI 동일 개념 판정 |
| PATCH | /contributions/duplicates | write | 병합·분리·보류 결정 |
| POST | /chat | read | 용어집에 근거한 AI 질문 |
| GET | /terms | read | 용어 목록·검색 |
| POST | /terms | write | 용어 등록 |
| GET | /terms/{idOrSlug} | read | 용어 상세 |
| PATCH | /terms/{idOrSlug} | write | 용어 수정 (낙관적 잠금) |
| DELETE | /terms/{idOrSlug} | admin | 용어 삭제 |
| GET | /terms/{idOrSlug}/revisions | read | 수정 이력 |
| POST | /terms/{idOrSlug}/revisions/{number}/revert | write | 그 리비전으로 되돌리기 (새 리비전이 쌓인다) |
| POST | /terms/lookup | read | 배치 표기 조회 (AI-Lint 통합 지점) |
| GET | /terms/suggest | read | 검색창 자동완성 후보 (최대 8개) |
| GET | /relations | read | 의미 관계 목록·근거·재검토 상태 |
| GET | /relations/terms | read | 관계 편집용 용어 검색·정의·리비전 |
| POST | /relations | 로그인 사용자 | 의미 관계 제안 |
| PATCH | /relations/{id} | 로그인 사용자 | 관계 수정·승인·거절 |
| POST | /import | write | 엑셀 임포트 (dry-run 기본) |
| POST | /attachments | write | 본문 이미지 업로드·WebP 변환 |
| GET | /attachments/{sha256} | read | 내용 해시로 첨부 이미지 조회 |
브라우저가 오가는 SSO 창구 두 개(/auth/sso/start, /auth/sso/callback)는 /api/v1 바깥에 있다. 화면 이동으로만 답하는 자리라 JSON 에러 봉투를 쓸 수 없기 때문이다 — 실패는 /login?sso=<코드>로 돌아온다. SSO 연결에 코드표가 있다.
인증
인증은 두 갈래다.
| 주체 | 방식 |
|---|---|
| 사람(웹 UI) | glossary_session 쿠키 또는 신뢰하도록 구성한 oauth2-proxy 헤더 |
| 도구(AI-Lint, CI) | Authorization: Bearer glk_<prefix>_<secret> |
Authorization 헤더가 있으면 API 키 경로를, 없으면 세션 경로를 탄다. 스킴 토큰(Bearer)은 RFC 7235대로 대소문자를 구분하지 않지만 토큰 본문은 구분한다.
API 키는 해시만 저장한다. 평문 토큰은 발급 응답에서만 볼 수 있고 이후로는 복구할 수 없다. 자세한 것은 인증에 있다.
scope
키마다 read / write / validate 중 하나 이상을 갖는다. 요구 scope가 없으면 403 forbidden이다. 세션 사용자는 scope 대신 role(admin | editor | viewer)로 갈린다. viewer는 read/validate 요청만 할 수 있고, editor와 admin은 write scope도 사용할 수 있다. 용어 삭제와 /admin/*의 홈·AI·사용자 설정처럼 명시된 관리 작업은 admin 전용이다.
에러 규약
전 엔드포인트가 같은 봉투를 쓴다. 예외 없음.
{
"error": {
"code": "revision_conflict",
"message": "다른 사람이 이미 수정했습니다.",
"details": { "currentRevision": 7 }
}
}details는 있을 때만 실린다. code는 기계가 분기할 수 있는 안정된 문자열이다.
| code | HTTP | 언제 |
|---|---|---|
validation_failed | 400 | 입력이 스키마에 맞지 않음. details에 필드별 사유 |
unauthorized | 401 | 세션도 유효한 키도 없음 |
forbidden | 403 | 인증은 됐으나 scope/role이 모자람 |
not_found | 404 | 대상 없음. 형식이 잘못된 id도 여기로 온다 |
term_not_found | 404 | 용어 대상이 없음 |
revision_conflict | 409 | 낙관적 잠금 충돌. details.currentRevision |
email_taken | 409 | 가입하려는 이메일이 이미 있음(대소문자 무시) |
payload_too_large | 413 | 업로드 본문 상한 초과 |
rate_limited | 429 | 챗봇의 사용자·API Key별 분당 요청 제한 초과 |
method_not_allowed | 405 | Allow 헤더에 실제 허용 메서드가 실려 온다 |
internal_error | 500 | 처리되지 않은 예외. 스택은 응답에 노출하지 않는다 |
ai_provider_error | 502 | 선택 모델·인증·할당량 또는 AI 공급자 연결 문제 |
ai_not_enabled | 503 | 관리자가 용어 챗봇 연결을 활성화하지 않음 |
rag_provider_error | 502 | Embedding/Reranker 공급자 연결·인증·응답 문제 |
rag_not_ready | 503 | RAG가 꺼져 있거나 색인/비밀값 설정이 준비되지 않음 |
4xx와 5xx의 경계
재시도해도 절대 성공하지 않는 입력은 5xx가 아니라 4xx다. ?page=1e999이나 형식이 잘못된 UUID는 그대로 Postgres로 흘러가면 500이 되지만, 기계 클라이언트에게 "나중에 다시 시도하라"는 신호를 주면 안 되므로 라우트가 미리 걸러 400/404로 답한다.
405와 Allow 헤더
라우트는 자신이 처리하지 않는 메서드를 명시적으로 405 스텁으로 export한다. Next의 기본 405는 0바이트 본문에 content-type도 없어 위 규약을 깬다. OPTIONS도 함께 만들어져서 Allow 헤더가 실제 허용 메서드와 항상 일치한다 (GET을 허용하는 라우트는 HEAD도 같이 광고한다).
$ curl -i -X PUT http://localhost:3000/api/v1/terms
HTTP/1.1 405 Method Not Allowed
allow: GET, HEAD, POST
{"error":{"code":"method_not_allowed","message":"지원하지 않는 메서드입니다."}}상태를 바꾸는 GET은 만들지 않는다
CSRF 방어는 상태 변경 요청의 Origin 또는 Referer를 허용 출처와 비교하고 SameSite=Lax 쿠키를 함께 사용한다. 따라서 상태 변경은 반드시 POST/PATCH/DELETE다. 로그아웃이 GET이 아니라 POST인 이유가 이것이다. apps/web/tests/screen-guards.test.ts가 이 규칙을 강제한다.
HTTPS
기본 Compose 구성은 평문 HTTP다. 세션 쿠키는 HTTPS 요청에서 Secure가 자동으로 붙는다. 판정은 apps/web/src/lib/auth/session.ts의 isSecureRequest가 담당한다. 운영에서 프록시 헤더를 신뢰하려면 GLOSSARY_TRUST_PROXY_HEADERS=true를 설정하고, 리버스 프록시는 X-Forwarded-Proto를 실제 외부 프로토콜로 덮어써야 한다. 구성 전제는 운영 안내서를 참고한다.