본문으로 건너뛰기

REST API 외부 통합

GenD admin 이 발급한 M2M 서비스 클라이언트 (gend-svc-*) 자격증명으로 외부 서비스가 GenD REST API 를 호출하는 시나리오별 빠른 시작.

운영자 관점 매트릭스/스펙은 외부 연동 가이드 — base URL, 권한, 에러 shape 의 단일 진실 공급원. 본 튜토리얼은 외부 개발자가 자격증명만으로 시작하는 절차에 집중한다.

사전 조건

GenD admin 이 /admin/service-clients 에서 다음을 발급해 둔 상태.

항목예시
Client IDgend-svc-quant-ai
Client Secret발급 모달에서 1회 표시되는 값
Roleviewer 또는 analyst
Base URLhttps://gend.genon.ai

발급 모달을 닫으면 시크릿은 다시 볼 수 없다 — 즉시 시크릿 매니저에 저장.

공통 흐름

시나리오 A — curl

CI 잡, ad-hoc 디버깅, shell 스크립트.

환경변수

export GEND_BASE_URL="https://gend.genon.ai"
export GEND_CLIENT_ID="gend-svc-quant-ai"
export GEND_CLIENT_SECRET="<발급 모달에서 복사>"

토큰 발급

TOKEN=$(curl -sS -X POST \
"$GEND_BASE_URL/auth/realms/gend/protocol/openid-connect/token" \
-d grant_type=client_credentials \
-d client_id="$GEND_CLIENT_ID" \
-d client_secret="$GEND_CLIENT_SECRET" \
| jq -r .access_token)

echo "${TOKEN:0:40}..."

API 호출

# 헬스체크 (인증 불필요)
curl -sS "$GEND_BASE_URL/health"

# 카탈로그 목록 (viewer)
curl -sS -H "Authorization: Bearer $TOKEN" \
"$GEND_BASE_URL/api/v1/catalog/catalogs"

# SQL 실행 (analyst)
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT count(*) FROM hive.default.users"}' \
"$GEND_BASE_URL/api/v1/query/execute"

# OpenAPI 스펙 (인증 불필요)
curl -sS "$GEND_BASE_URL/openapi.json" -o gend-openapi.json

GET /health 는 prefix 없음 — GET /api/v1/health 는 의도적으로 404. 자세한 매트릭스는 엔드포인트 매트릭스 참조.

시나리오 B — Python httpx

장기 실행 서비스, 데이터 파이프라인, FastAPI 백엔드의 GenD 어댑터.

설치

pip install httpx

클라이언트 클래스 (토큰 캐시 + 401 retry)

# gend_client.py
import os
import time
import httpx


class GendClient:
"""GenD REST API client with M2M token caching and 401 retry."""

def __init__(self):
self.base = os.environ["GEND_BASE_URL"].rstrip("/")
self.realm = os.environ.get("GEND_REALM", "gend")
self.client_id = os.environ["GEND_CLIENT_ID"]
self.client_secret = os.environ["GEND_CLIENT_SECRET"]
self._token: str | None = None
self._exp: float = 0.0

def _ensure_token(self) -> None:
if self._token and time.time() < self._exp - 60:
return
r = httpx.post(
f"{self.base}/auth/realms/{self.realm}/protocol/openid-connect/token",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
},
timeout=10,
)
r.raise_for_status()
body = r.json()
self._token = body["access_token"]
self._exp = time.time() + body["expires_in"]

def _request(self, method: str, path: str, **kw) -> httpx.Response:
self._ensure_token()
url = f"{self.base}{path}"
headers = {"Authorization": f"Bearer {self._token}", **kw.pop("headers", {})}
r = httpx.request(method, url, headers=headers, timeout=30, **kw)
if r.status_code == 401:
# 토큰 회전/만료 후 한 번 더 시도
self._token = None
self._exp = 0
self._ensure_token()
headers["Authorization"] = f"Bearer {self._token}"
r = httpx.request(method, url, headers=headers, timeout=30, **kw)
r.raise_for_status()
return r

def list_catalogs(self) -> list[dict]:
return self._request("GET", "/api/v1/catalog/catalogs").json()

def execute_query(self, sql: str) -> dict:
return self._request(
"POST", "/api/v1/query/execute", json={"sql": sql}
).json()

def ask(self, question: str) -> dict:
return self._request(
"POST", "/api/v1/ai/ask", json={"question": question}
).json()

사용

from gend_client import GendClient

client = GendClient()
print(client.list_catalogs())
print(client.execute_query("SELECT 1 AS ping"))

토큰은 만료 60초 전까지 재사용되고, 401 응답 시 자동 재발급+재시도.

에러 응답 처리

GenD 는 현재 두 가지 shape 을 반환 — string 또는 Pydantic ValidationError list:

def parse_detail(response: httpx.Response) -> str:
try:
body = response.json()
detail = body.get("detail", "")
if isinstance(detail, list):
# Pydantic ValidationError
return "; ".join(d.get("msg", "") for d in detail)
return str(detail)
except (ValueError, AttributeError):
return response.text[:200]

RFC 7807 Problem Details 표준화 후속 이슈에서 {type, title, status, detail, instance, code} 로 통일 예정. 그 전까지는 위 분기 패턴을 사용한다.

시나리오 C — LangChain Agent Tool

LLM 에이전트가 GenD 카탈로그·NL2SQL·RAG 를 도구로 호출하는 패턴.

설치

pip install langchain langchain-anthropic httpx

도구 정의

# gend_tools.py
from langchain.tools import tool
from gend_client import GendClient

_client = GendClient()


@tool
def list_gend_catalogs() -> list[dict]:
"""List all data catalogs available in GenD."""
return _client.list_catalogs()


@tool
def gend_sql(sql: str) -> dict:
"""Execute a Trino SQL query against GenD. Read-only.

Args:
sql: Trino SQL — must be SELECT only, no DDL/DML.
"""
return _client.execute_query(sql)


@tool
def gend_ask(question: str) -> dict:
"""Ask GenD in natural language — NL2SQL converts to Trino SQL and executes.

Args:
question: Natural language question about the data, e.g. "지난 주 주문 수".
"""
return _client.ask(question)

Agent 구성

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate

from gend_tools import gend_ask, gend_sql, list_gend_catalogs

llm = ChatAnthropic(model="claude-sonnet-4-6", temperature=0)

prompt = ChatPromptTemplate.from_messages([
("system", "You are a data analyst. Use GenD tools to answer questions about the data."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])

tools = [list_gend_catalogs, gend_ask, gend_sql]
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({"input": "오늘 가장 많이 팔린 상품 카테고리 5개를 알려줘"})
print(result["output"])

GenD 의 NL2SQL (/api/v1/ai/ask) 가 자연어를 Trino SQL 로 변환하고 실행한다 — LLM 이 SQL 을 직접 작성할 필요 없음. SQL 실행 권한을 더 좁게 가져가려면 gend_ask 만 노출하고 gend_sql 은 제거.

권한 매트릭스 요약

호출필요 role
GET /health, GET /openapi.json(인증 불필요)
GET /api/v1/catalog/*, quality/*, governance/*, feature/*viewer
POST /api/v1/query/execute, ai/ask, rag/queryanalyst
POST/PUT/DELETE 변경 작업admin (M2M client 자동 거부 — admin role 분리 발급 필요)

전체 매트릭스는 외부 연동 가이드의 권한 매트릭스.

토큰 라이프사이클

항목
Access Token 수명30분 — 단, 클라이언트는 응답의 expires_in 을 사용할 것
Refresh Token사용 안 함 (client_credentials 표준)
갱신 전략expires_in 기준 만료 30~60초 전 재발급, 401 응답 시 한 번 더

트러블슈팅

증상원인 / 해결
400 unauthorized_clientclient_secret 불일치 — admin 의 secret 회전 후 외부 미반영
401 Token expired토큰 만료 (30분) — 클라이언트 캐시 갱신
401 Invalid audienceservice client 의 gend-api audience mapper 누락 (UI 발급 시 자동)
403 권한 부족service-account realm role 미부여 — admin 에 부여 절차 요청
변경 작업 거부M2M 토큰은 자동 admin 승격 없음 — admin role 분리 client 발급 필요
/openapi.json 이 HTML 반환(구버전 ingress) — PR #662 머지 후 정상 JSON

다음 단계