API 기초

REST, HTTP 메서드, JSON, 상태 코드 – 백엔드 통신의 기본 문법

API(Application Programming Interface)는 프로그램 간 통신 규약이다. REST 아키텍처, HTTP 요청-응답 구조, JSON 직렬화, 상태 코드의 의미를 정리하고, AI Agent를 서빙할 때 알아야 할 최소한의 웹 API 지식을 다룬다.

Engineering
저자

Kwangmin Kim

공개

2026년 05월 05일

1 왜 API를 알아야 하는가

  • 모델을 학습시키거나 파이프라인을 구축하는 것과 그 결과를 다른 시스템이 사용할 수 있게 만드는 것은 별개의 문제이다.
  • Jupyter 노트북에서 잘 돌아가는 RAG 파이프라인이 있더라도, 프론트엔드 개발자가 그 결과를 화면에 보여주려면 프로그래밍 언어와 플랫폼에 관계없이 소통하는 표준 인터페이스가 필요하다. 이것이 API이다.

API를 모르면:

  • 모델이 Jupyter 안에 갇힌다 – 동료, 사용자, 다른 시스템이 접근할 수 없다
  • 프론트엔드/백엔드 개발자와 대화할 때 엔드포인트, 페이로드, 상태 코드 같은 용어를 이해하지 못한다
  • FastAPI, Flask 같은 프레임워크를 사용하더라도 “왜 이렇게 설계하는가”를 모른 채 복붙하게 된다

2 API란 무엇인가

정의: API (Application Programming Interface)

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 아키텍처

정의: REST (Representational State Transfer)
  • 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 /feedback

LLM 호출은 토큰을 소비하고 로그를 남기는 부수 효과(side effect)가 있으므로 항상 POST를 사용한다. GET은 부수 효과가 없는 순수 조회에만 사용한다.

6 JSON: API의 공용어

정의: JSON (JavaScript Object Notation)

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}를 넣는 방식은 안티패턴이다.

그 이유는:

  1. 모니터링 도구가 상태 코드로 오류율을 집계한다. 모두 200이면 장애를 감지할 수 없다
  2. 클라이언트 라이브러리(fetch, axios)가 상태 코드로 성공/실패를 분기한다
  3. 로드 밸런서가 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란 무엇인가?"))

httpxrequests와 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 관련 주제

선행 지식

후속 주제

다른 카테고리 연결

Subscribe

Enjoy this blog? Get notified of new posts by email: