1 왜 API를 알아야 하는가
- 모델을 학습시키거나 파이프라인을 구축하는 것과 그 결과를 다른 시스템이 사용할 수 있게 만드는 것은 별개의 문제이다.
- Jupyter 노트북에서 잘 돌아가는 RAG 파이프라인이 있더라도, 프론트엔드 개발자가 그 결과를 화면에 보여주려면 프로그래밍 언어와 플랫폼에 관계없이 소통하는 표준 인터페이스가 필요하다. 이것이 API이다.
API를 모르면:
- 모델이 Jupyter 안에 갇힌다 – 동료, 사용자, 다른 시스템이 접근할 수 없다
- 프론트엔드/백엔드 개발자와 대화할 때 엔드포인트, 페이로드, 상태 코드 같은 용어를 이해하지 못한다
- FastAPI, Flask 같은 프레임워크를 사용하더라도 “왜 이렇게 설계하는가”를 모른 채 복붙하게 된다
2 API란 무엇인가
API는 두 소프트웨어 시스템이 서로 통신하기 위한 규약(contract)이다. 요청하는 쪽(클라이언트)과 응답하는 쪽(서버)이 어떤 형식으로 데이터를 주고받을지 미리 합의한 인터페이스를 말한다.
일상적 비유로 설명하면, 식당의 메뉴판이 API에 해당한다.
메뉴판에는
“무엇을 주문할 수 있고(엔드포인트),
어떤 형식으로 주문해야 하며(요청 형식),
어떤 형태로 나오는지(응답 형식)”
가 정의되어 있다. 주방(서버)의 내부 구현을 몰라도 메뉴판만 보면 주문할 수 있다.
2.1 API의 종류
| 종류 | 프로토콜 | 특징 | 사용 사례 |
|---|---|---|---|
| REST API | HTTP | 가장 보편적, URL + HTTP 메서드로 자원 조작 | 웹 서비스, 모바일 앱, AI Agent 서빙 |
| GraphQL | HTTP | 클라이언트가 필요한 필드만 선택적으로 요청 | 복잡한 데이터 관계가 있는 프론트엔드 |
| gRPC | HTTP/2 | Protocol Buffers 기반 이진 직렬화, 빠른 속도 | 마이크로서비스 간 내부 통신 |
| WebSocket | TCP | 양방향 실시간 통신 | 채팅, 실시간 대시보드 |
이 포스트에서는 REST API에 집중한다. AI Agent를 서빙할 때 가장 흔히 사용하는 방식이며, FastAPI가 기본적으로 REST를 따르기 때문이다.
3 REST 아키텍처
- Representational State Transfer는 ‘자원의 상태를 나타내는 표현 상태 전송’ 로 간단히 번역할 수 있고 약어로 REST라고 불린다.
- 즉, 서버의 자원을 직접 전송하는 대신, 그 자원의 표현을 클라이언트로 전송한다는 의미이다.
- REST는 웹 API를 설계하는 아키텍처 스타일이다. 2000년 Roy Fielding의 박사 논문에서 제안되었으며, HTTP 프로토콜의 기존 메커니즘(URL, 메서드, 상태 코드)을 활용하여 자원(resource)을 조작하는 방식을 정의한다.
- 특정 프로토콜이나 기술에 종속되지 않으며 HTTP와 함께 사용할 때 가장 널리 알려져 있지만, REST의 원칙은 다른 프로토콜에도 적용될 수 있다.
3.1 REST의 핵심 원칙
| 원칙 | 설명 | 위반 시 문제 |
|---|---|---|
| 자원 기반(Resource-Based) | 모든 것을 URL로 식별 가능한 자원으로 표현한다. /users/42는 ID가 42인 사용자 자원이다 |
URL이 동사가 되면(/getUser) 일관성이 깨진다 |
| 무상태(Stateless) | 각 요청은 독립적이다. 서버는 이전 요청의 맥락을 기억하지 않는다. 필요한 정보는 매 요청에 포함한다 | 서버가 세션을 유지하면 수평 확장(scale-out)이 어려워진다 |
| 표준 인터페이스(Uniform Interface) | HTTP 메서드(GET, POST, PUT, DELETE)로 자원에 대한 연산을 표현한다 | 커스텀 동사를 만들면 API 사용자가 학습 비용을 치른다 |
| 표현 분리(Representation) | 자원 자체와 자원의 표현(JSON, XML)을 분리한다. 같은 자원을 JSON으로도, XML로도 반환할 수 있다 | 대부분의 현대 API는 JSON만 사용하므로 실무에서는 덜 중요하다 |
- “URL이 동사가 된다” 는 것은 경로 자체에 동작을 넣는 경우로
/getUser?id=42,/runAgent와 같이 URL이 ‘무엇을 할지’ 직접 지시하는 것을 의미한다. - REST 에서는 “자원(resource)”이 ’무엇’인지를 식별하는 것이 URL의 책임이고, “어떤 연산을 할지”는 HTTP 메서드(GET/POST/PUT/DELETE 등)가 담당해야 한다는 전제를 깔고 있다. URL이 동사가 되면 일관된 인터페이스와 예측 가능한 캐싱·멱등성 모델을 해친다.
- 예를 들어, url은
/users/42(사용자 42),/orders/123/items(주문 항목 목록). 그 자원의 상태(state)는 JSON/XML 같은 표현(representation)으로 전송된다. 즉, “동사형 경로”가 아니라 “명사(자원) + 메서드(동작)”가 원칙이며, ‘POST(메서드) /agents/qna_chatbot/runs’ (자원) 같은 형태가 권장된다.
3.2 REST에서의 자원 설계
REST API의 URL은 명사(자원)로, HTTP 메서드는 동사(연산)로 구성한다.
좋은 설계:
GET /agents/qna_chatbot → 에이전트 정보 조회
POST /agents/qna_chatbot/run → 에이전트 실행
GET /agents/qna_chatbot/documents → 문서 목록 조회
나쁜 설계:
GET /getAgent?name=qna_chatbot → URL에 동사가 들어간다
POST /runAgent → 자원 계층이 없다
4 HTTP 요청-응답 구조
클라이언트와 서버의 모든 통신은 요청(Request)과 응답(Response) 쌍으로 이루어진다.
4.1 요청 (Request)
POST /agents/qna_chatbot/run HTTP/1.1 ← 요청 라인 (메서드 + 경로 + 프로토콜)
Host: localhost:8000 ← 헤더
Content-Type: application/json ← 헤더: 본문 형식
Authorization: Bearer eyJ... ← 헤더: 인증 토큰
{ ← 본문 (Body/Payload)
"text": "RAG란 무엇인가?",
"history": [],
"user_id": "user_42"
}
요청은 네 가지 구성 요소를 가진다:
| 구성 요소 | 설명 | 예시 |
|---|---|---|
| 메서드(Method) | 수행할 연산의 종류 | GET, POST, PUT, DELETE |
| 경로(Path/URL) | 대상 자원의 위치 | /agents/qna_chatbot/run |
| 헤더(Headers) | 메타정보 (인증, 콘텐츠 타입 등) | Content-Type: application/json |
| 본문(Body) | 전송할 데이터 (GET에는 보통 없음) | {"text": "질문 내용"} |
4.2 응답 (Response)
HTTP/1.1 200 OK ← 상태 라인 (프로토콜/버전 + 상태 코드 + 메시지)
Content-Type: application/json ← 헤더
{ ← 본문
"response": {
"text": "RAG는 검색 증강 생성으로...",
"citations": [...],
"run_id": "abc-123"
}
}
5 HTTP 메서드
HTTP 메서드는 자원에 대해 어떤 연산을 수행할지 선언한다.
| 메서드 | 연산 | 멱등성 | 본문 | 대표 용도 |
|---|---|---|---|---|
GET |
조회(Read) | 멱등 | 없음 | 데이터 가져오기 |
POST |
생성(Create) / 실행 | 비멱등 | 있음 | 새 자원 생성, 에이전트 실행 |
PUT |
전체 수정(Update) | 멱등 | 있음 | 자원 전체 교체 |
PATCH |
부분 수정(Partial Update) | 비멱등 | 있음 | 자원 일부 변경 |
DELETE |
삭제(Delete) | 멱등 | 없음 | 자원 삭제 |
멱등성(Idempotency)은 같은 요청을 여러 번 보내도 결과가 동일한 성질이다. GET /users/42를 10번 호출해도 사용자 42의 정보는 동일하다. 반면 POST /users를 10번 호출하면 사용자가 10명 생성될 수 있다.
PUT이 멱등인 이유는 PUT은 대상 자원을 “주어진 표현으로 완전히 교체”하므로, 같은 URL에 같은 바디로 여러 번 보내면(1회든 10회든) 결과적으로 자원은 동일한 상태를 유지한다. 즉 첫 호출에서 변경은 일어나지만, 이후 동일 요청은 추가 변경을 일으키지 않는다.
5.1 AI Agent 서빙에서의 메서드 선택
AI Agent를 API로 서빙할 때 가장 흔한 패턴이다:
# Agent 실행 — POST (부수 효과: 토큰 소비, 로그 기록)
POST /agents/qna_chatbot/run
POST /agents/qna_chatbot/stream
# 문서 목록 조회 — GET (부수 효과 없음)
GET /agents/qna_chatbot/documents
# 메트릭 조회 — GET
GET /monitoring/metrics
# 피드백 제출 — POST (새 자원 생성)
POST /feedbackLLM 호출은 토큰을 소비하고 로그를 남기는 부수 효과(side effect)가 있으므로 항상 POST를 사용한다. GET은 부수 효과가 없는 순수 조회에만 사용한다.
6 JSON: API의 공용어
JSON은 사람이 읽을 수 있는 텍스트 기반의 데이터 교환 형식이다. 키-값 쌍의 중첩 구조로, 프로그래밍 언어에 관계없이 데이터를 표현할 수 있다.
6.1 왜 JSON인가
API 통신에서 데이터를 주고받으려면 양쪽이 이해하는 공통 형식이 필요하다. XML, YAML, Protocol Buffers 등 여러 선택지가 있지만, 현대 웹 API는 대부분 JSON을 사용한다.
| 형식 | 장점 | 단점 |
|---|---|---|
| JSON | 가볍다, 가독성 좋다, 모든 언어에서 파싱 가능 | 스키마 강제 없음, 이진 데이터 비효율 |
| XML | 스키마 검증 가능, 네임스페이스 지원 | 장황하다, 파싱이 느리다 |
| Protocol Buffers | 빠르다, 타입 안전, 작은 크기 | 사람이 읽기 어렵다, .proto 파일 필요 |
6.2 JSON 문법
{
"text": "RAG란 무엇인가?",
"history": [
{"role": "user", "content": "이전 질문"},
{"role": "assistant", "content": "이전 답변"}
],
"agent_params": {
"mode": "data",
"temperature": 0.7
},
"user_id": "user_42",
"stream": true
}| 데이터 타입 | JSON 표현 | Python 대응 |
|---|---|---|
| 문자열 | "hello" |
str |
| 숫자 | 42, 3.14 |
int, float |
| 불리언 | true, false |
True, False |
| 배열 | [1, 2, 3] |
list |
| 객체 | {"key": "value"} |
dict |
| 널 | null |
None |
6.3 Python에서의 JSON 변환
- 직렬화: 객체(데이터 구조)를 전송하거나 저장할 수 있는 바이트/문자열 형태로 변환하는 과정
- 역과정은 역직렬화(deserialization)로 바이트/문자열 → 원래 객체로 변환
- 목적: 네트워크 전송, 파일 저장, 프로세스 간 통신, 캐싱, 원격 호출(RPC) 등에서 사용한다.
- 예: Python dict → JSON 문자열
{"x":1,"y":2}(직렬화), JSON → dict (역직렬화)
import json
# Python dict → JSON 문자열 (직렬화)
data = {"text": "질문", "history": [], "user_id": "user_42"}
json_str = json.dumps(data, ensure_ascii=False)
# '{"text": "질문", "history": [], "user_id": "user_42"}'
# JSON 문자열 → Python dict (역직렬화)
parsed = json.loads(json_str)
# {'text': '질문', 'history': [], 'user_id': 'user_42'}FastAPI에서는 이 변환을 프레임워크가 자동으로 처리한다.
Pydantic 모델을 정의하면 요청 JSON을 Python 객체로, Python 객체를 응답 JSON으로 변환해 준다.
7 HTTP 상태 코드
서버는 요청 처리 결과를 3자리 숫자 코드로 알려준다. 첫 자리 숫자로 범주가 결정된다.
| 범주 | 의미 | 대표 코드 |
|---|---|---|
| 1xx | 정보성 | 100 Continue — 거의 사용하지 않는다 |
| 2xx | 성공 | 200 OK, 201 Created, 204 No Content |
| 3xx | 리다이렉션 | 301 Moved Permanently, 304 Not Modified |
| 4xx | 클라이언트 오류 | 400 Bad Request, 401 Unauthorized, 404 Not Found, 422 Unprocessable Entity |
| 5xx | 서버 오류 | 500 Internal Server Error, 503 Service Unavailable |
7.1 자주 사용하는 상태 코드 상세
| 코드 | 의미 | AI Agent API에서의 용도 |
|---|---|---|
200 OK |
요청 성공 | 에이전트 실행 성공, 문서 조회 성공 |
201 Created |
자원 생성 성공 | 새 피드백 제출 성공 |
400 Bad Request |
요청 형식이 잘못됨 | 필수 필드 누락, JSON 파싱 실패 |
401 Unauthorized |
인증 실패 | API 키 누락 또는 만료 |
404 Not Found |
자원이 존재하지 않음 | 존재하지 않는 에이전트 이름으로 요청 |
422 Unprocessable Entity |
형식은 맞지만 내용이 유효하지 않음 | Pydantic 검증 실패 (FastAPI 기본) |
429 Too Many Requests |
요청 속도 제한 초과 | Azure OpenAI TPM(분당 토큰) 쿼터 초과 |
500 Internal Server Error |
서버 내부 오류 | LLM 호출 실패, 예외 처리 누락 |
503 Service Unavailable |
서비스 일시 중단 | 모델 로딩 중, warmup 미완료 |
7.2 상태 코드를 올바르게 사용하는 이유
상태 코드를 무시하고 모든 응답을 200으로 보내면서 본문에 {"error": true}를 넣는 방식은 안티패턴이다.
그 이유는:
- 모니터링 도구가 상태 코드로 오류율을 집계한다. 모두 200이면 장애를 감지할 수 없다
- 클라이언트 라이브러리(fetch, axios)가 상태 코드로 성공/실패를 분기한다
- 로드 밸런서가 5xx 응답을 보고 서버를 헬스체크에서 제외한다
8 엔드포인트 설계 패턴
8.1 URL 구조
https://api.example.com/v1/agents/qna_chatbot/run
| 구성 | 역할 | 예시 |
|---|---|---|
| Scheme | 프로토콜 | https |
| Host | 서버 주소 | api.example.com, localhost:8000 |
| Version | API 버전 | /v1, /v2 (선택) |
| Resource Path | 자원 경로 | /agents/qna_chatbot/run |
| Query String | 필터링/페이징 | ?page=1&limit=10 |
- ://는 URL에서 스킴(프로토콜)과 호스트(권한)를 구분하는 표기법이다.
- : 은 스킴과 나머지의 구분자. 예: https: → 스킴이 https임을 표시.
- // 은 뒤에 “authority”가 온다는 표시(사용자정보@호스트:포트 형태 가능). 즉 네트워크 위치가 뒤따름을 뜻함.
- ?는 URL에서 경로(path)와 쿼리 문자열(query string)을 구분하는 구분자이다.
- ?는 한 URL에 한 번만 사용(첫 ? 이후는 모두 쿼리로 처리)
- &는 여러 쿼리 매개변수를 연결할 때 사용한다.
- #은 쿼리와 별개로 클라이언트 측 프래그먼트다.
8.2 경로 매개변수 vs 쿼리 매개변수
# 경로 매개변수: 자원을 식별하는 필수 값
GET /agents/{agent_name}/documents
GET /agents/qna_chatbot/documents # agent_name이 qna_chatbot인 문서 조회
# 쿼리 매개변수: 선택적 필터링/정렬/페이징
GET /monitoring/metrics?agent=qna_chatbot&period=7d
경로 매개변수는 자원의 정체성을 결정한다. 쿼리 매개변수는 결과의 표현 방식을 조정한다.
9 코드 예시: Python에서 API 호출하기
9.1 requests 라이브러리
import requests # Python의 HTTP 클라이언트 라이브러리(API 호출용 도구) — 서버(백엔드) 자체는 아님.
# GET 요청: 문서 목록 조회
response = requests.get("http://localhost:8000/agents/qna_chatbot/documents")
print(response.status_code) # 200
print(response.json()) # [{"title": "...", "source": "..."}]
# POST 요청: 에이전트 실행
payload = {
"text": "RAG란 무엇인가?",
"history": [],
"user_id": "user_42"
}
response = requests.post(
"http://localhost:8000/agents/qna_chatbot/run",
json=payload
)
print(response.status_code) # 200
result = response.json()
print(result["response"]["text"])9.2 httpx (비동기 지원)
import httpx
import asyncio
async def ask_agent(question: str) -> str:
async with httpx.AsyncClient() as client:
response = await client.post(
"http://localhost:8000/agents/qna_chatbot/run",
json={"text": question, "history": [], "user_id": "user_42"},
timeout=60.0
)
response.raise_for_status()
return response.json()["response"]["text"]
answer = asyncio.run(ask_agent("RAG란 무엇인가?"))httpx는 requests와 API가 거의 동일하면서 비동기를 지원한다. FastAPI와 함께 사용할 때 자연스럽다.
10 API 문서화: OpenAPI/Swagger
FastAPI는 코드에서 API 문서를 자동 생성한다. 서버를 실행하면 다음 URL에서 확인할 수 있다:
- Swagger UI:
http://localhost:8000/docs– 인터랙티브 테스트 가능 - ReDoc:
http://localhost:8000/redoc– 정적 문서
이 자동 문서화가 가능한 이유는 FastAPI가 Python의 타입 힌트와 Pydantic 모델을 읽어 OpenAPI 명세(JSON 스키마)를 생성하기 때문이다. API를 설계하면 문서가 자동으로 따라오므로 별도 문서 작성 비용이 없다.
11 관련 주제
선행 지식
- HTTP Methods – HTTP 메서드 상세
후속 주제
- FastAPI 입문 – Python으로 REST API 서버를 만드는 프레임워크
- Pydantic – 데이터 검증과 직렬화 – 요청/응답 스키마를 안전하게 정의하는 방법
- CORS와 Proxy – 프론트엔드에서 백엔드 API를 호출할 때 만나는 벽
다른 카테고리 연결
- Agent 시스템의 구현 코드와 LangChain/LangGraph 활용 – API로 서빙하는 Agent 시스템의 구현 사례