외부 결제 연동이 갑자기 실패했다는 긴급 알림이 들어왔습니다. 개발팀은 "API 호출이 실패하는 것 같다"고 하는데, 정확히 어디서 왜 실패하는지 모릅니다. 로그를 보니 HTTP 500이 간헐적으로 찍힙니다. 그런데 직접 API를 호출해서 확인해볼 수 있는 인프라 엔지니어가 없습니다.
개발자가 코드를 뜯어보는 동안 인프라 엔지니어가 직접 curl로 API 엔드포인트를 찔러봤다면 5분 안에 원인을 좁힐 수 있었을 것입니다. 이 모듈은 그 5분을 만드는 기초입니다.
- 1HTTP 메서드(GET/POST/PUT/PATCH/DELETE)의 용도와 멱등성 차이를 설명할 수 있다
- 2주요 상태코드(2xx/4xx/5xx)의 의미를 보고 즉시 원인 범위를 좁힐 수 있다
- 3curl로 인증 헤더 포함 GET/POST 요청을 보내고 응답을 확인할 수 있다
- 4jq로 JSON 응답에서 원하는 필드를 파싱할 수 있다
- 5API 응답 시간을 측정해서 성능 이상 여부를 판단할 수 있다
HTTP 메서드와 상태코드
HTTP 메서드 — 동작 의미와 멱등성
REST API를 처음 보면 URL보다 메서드가 더 중요합니다. 같은 /users/123이라도 GET이면 조회, DELETE면 삭제입니다. 메서드를 틀리면 원하는 동작이 일어나지 않거나, 의도치 않은 변경이 발생합니다.

| 메서드 | 용도 | 멱등성 | 요청 바디 |
|---|---|---|---|
| GET | 리소스 조회 | O | 없음 |
| POST | 새 리소스 생성 | X | 있음 |
| PUT | 리소스 전체 교체 | O | 있음 |
| PATCH | 리소스 일부 수정 | 구현에 따라 다름 | 있음 |
| DELETE | 리소스 삭제 | O | 보통 없음 |
멱등성이 중요한 이유: 네트워크 타임아웃 등으로 응답을 못 받았을 때 재시도해도 되는 메서드인지 판단하는 기준이 됩니다. GET, PUT, DELETE는 재시도해도 안전하지만 POST는 중복 생성이 발생할 수 있습니다.
# 요청 시뮬레이션 — 메서드별 curl 패턴
curl -X GET https://api.example.com/users/123
curl -X POST https://api.example.com/users
curl -X PUT https://api.example.com/users/123
curl -X PATCH https://api.example.com/users/123
curl -X DELETE https://api.example.com/users/123
상태코드 — 범주별 의미 파악
API 장애 신고가 들어왔을 때 상태코드는 가장 먼저 확인해야 할 첫 번째 단서입니다. 코드 범주(2xx / 4xx / 5xx)만 보아도 문제가 클라이언트 요청에 있는지, 서버 내부에 있는지 즉시 구분할 수 있어 원인 추적 방향이 달라집니다. 특히 401과 403을 혼동하거나 502와 504를 같은 의미로 보면 점검 방향이 틀려지므로, 범주별 의미를 정확히 파악해두는 것이 인프라 엔지니어의 기본 역량입니다.

API 장애 대응에서 상태코드는 첫 번째 단서입니다. 코드 범주만 봐도 문제가 클라이언트 쪽인지 서버 쪽인지 구분됩니다.
2xx — 성공
| 코드 | 의미 | 주로 쓰이는 상황 |
|---|---|---|
| 200 OK | 요청 성공 | GET, PUT, PATCH 응답 |
| 201 Created | 리소스 생성 성공 | POST 응답, Location 헤더로 새 URL 전달 |
| 204 No Content | 성공하지만 응답 바디 없음 | DELETE, 일부 PUT 응답 |
4xx — 클라이언트 오류 (요청 자체의 문제)
| 코드 | 의미 | 확인할 것 |
|---|---|---|
| 400 Bad Request | 요청 형식/파라미터 오류 | 요청 바디 JSON 형식, 필수 파라미터 |
| 401 Unauthorized | 인증 실패 | Authorization 헤더, 토큰 만료 여부 |
| 403 Forbidden | 권한 없음 | 계정 권한 설정 |
| 404 Not Found | 리소스 없음 | URL 오타, 리소스 삭제 여부 |
| 415 Unsupported Media Type | Content-Type 불일치 | Content-Type 헤더 누락 또는 오타 |
| 422 Unprocessable Entity | 유효성 검사 실패 | 필드 형식, 값 범위 |
| 429 Too Many Requests | 요청 빈도 초과 | Rate Limit 설정, Retry-After 헤더 |
5xx — 서버 오류 (서버 측 문제)
| 코드 | 의미 | 확인할 것 |
|---|---|---|
| 500 Internal Server Error | 서버 내부 오류 | 서버 로그 (인프라 엔지니어 불필요, 앱 로그 확인) |
| 502 Bad Gateway | upstream 서버 비정상 | 뒷단 서비스 상태, 네트워크 연결 |
| 503 Service Unavailable | 서비스 일시 과부하 또는 점검 중 | 서버 부하, 배포 중 여부 |
| 504 Gateway Timeout | upstream 응답 지연 | 뒷단 서비스 응답 시간, 타임아웃 설정 |
요청 헤더 — 자주 쓰는 4가지
curl로 API를 호출했는데 401이 납니다. "Authorization 헤더 넣었는데요?"라고 하면 "어떤 형식으로 넣었어요?"라고 다시 묻습니다. Bearer인지 Basic인지, X-API-Key인지 서비스마다 다르고, Content-Type을 빠뜨리면 415가 납니다. 헤더 4가지의 역할을 구분해서 알아야 curl 테스트 한 번에 원인을 좁힐 수 있습니다.
헤더는 요청의 메타데이터입니다. API 인증, 데이터 형식, 클라이언트 식별 등 요청의 "문맥"을 담습니다. 인프라 엔지니어가 curl로 테스트할 때 헤더를 빠뜨리면 의미 있는 테스트가 되지 않습니다.
# 자주 쓰는 요청 헤더 4가지
Authorization: Bearer <JWT_TOKEN> # Bearer 토큰 인증 (OAuth2)
Authorization: Basic <BASE64> # Basic 인증 (user:pass를 base64 인코딩)
X-API-Key: <API_KEY> # API 키 인증 (서비스마다 헤더 이름이 다를 수 있음)
Content-Type: application/json # 요청 바디가 JSON임을 서버에 알림
Accept: application/json # 응답을 JSON으로 받고 싶다는 클라이언트 의사 표시
Authorization 유형 비교:
| 유형 | 형식 | 주로 쓰이는 곳 |
|---|---|---|
| Bearer | Authorization: Bearer <token> | REST API, OAuth2 (가장 흔함) |
| Basic | Authorization: Basic <base64> | 레거시 시스템, Jenkins, Artifactory |
| X-API-Key | X-API-Key: <key> | 외부 API 연동 (결제사, 공공 API) |
curl로 API 테스트하기
-s(silent)로 진행 표시를 숨기고, -H로 헤더를 추가합니다. | jq .로 응답 JSON을 들여쓰기 형태로 보기 좋게 출력합니다. jsonplaceholder는 실제 테스트용 공개 API입니다 — TOKEN 자리에 아무 문자열을 넣어도 동작합니다.
# 특정 필드만 추출
curl -s https://jsonplaceholder.typicode.com/users | jq '.[0].name'
curl -s https://jsonplaceholder.typicode.com/users | jq '.[].email'
curl -s https://jsonplaceholder.typicode.com/users | jq '.[0] | {id, name, email}'
curl -s -H 'Authorization: Bearer TOKEN' https://jsonplaceholder.typicode.com/users | jq- JSON이 들여쓰기된 형태로 출력됐는가 (jq 파싱 성공)
- jq '.[0].name' 으로 첫 번째 사용자 이름만 추출됐는가
- jq '.[].email' 로 전체 사용자 이메일 목록이 한 줄씩 출력됐는가
-X POST로 메서드를 지정하고, -H 'Content-Type: application/json'으로 바디 형식을 알립니다. -d '...'에 JSON 바디를 넣습니다. 실제 서비스 API는 201 Created를 반환하면서 생성된 리소스 정보를 응답합니다.
# 응답 상태코드와 헤더까지 확인
curl -s -o /dev/null -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d '{"name":"test","email":"test@example.com"}' \
https://jsonplaceholder.typicode.com/users
# 출력: 201
curl -s -X POST -H 'Content-Type: application/json' -d @/tmp/body.json https://jsonplaceholder.typicode.com/users- 응답 바디에 id 필드가 포함됐는가 (서버가 새 리소스를 생성했다는 증거)
- -w '%{http_code}' 옵션으로 201 상태코드가 반환됐는가
- Content-Type 헤더 없이 POST를 보냈을 때와 결과 차이가 있는가
-i(include)는 응답 헤더를 바디와 함께 출력합니다. Content-Type, Date, 캐시 헤더 등을 확인할 수 있습니다. 더 상세한 디버그가 필요하면 -v(verbose)를 씁니다.
# 요청/응답 전체 흐름 디버그 (연결, 헤더, 바디 모두 출력)
curl -v https://jsonplaceholder.typicode.com/users/1 2>&1 | head -40
# 응답 시간 측정 (API 성능 이상 확인)
curl -s -o /dev/null -w "time_total: %{time_total}s\ntime_connect: %{time_connect}s\n" \
https://jsonplaceholder.typicode.com/users/1
curl -i https://jsonplaceholder.typicode.com/users/1- -i 출력 첫 줄이 'HTTP/1.1 200 OK' 형태로 나왔는가
- 응답 헤더에 Content-Type: application/json이 포함됐는가
- -w time_total 값이 1초 이하인가 (해외 API의 경우 2-3초도 정상)
- curl -v 출력에서 '>' 로 시작하는 줄이 요청 헤더, '<' 로 시작하는 줄이 응답 헤더임을 확인했는가
jq는 JSON 데이터를 커맨드라인에서 필터링하는 도구입니다. API 응답에서 필요한 필드만 추출하거나, 배열을 순회하거나, 조건으로 필터링할 수 있습니다.
# 자주 쓰는 jq 패턴
curl -s https://jsonplaceholder.typicode.com/posts | jq '.[0]' # 첫 번째 객체
curl -s https://jsonplaceholder.typicode.com/posts | jq '.[].userId' # 모든 userId 추출
curl -s https://jsonplaceholder.typicode.com/posts | jq '.[] | select(.userId == 1)' # 필터
curl -s https://jsonplaceholder.typicode.com/posts | jq 'length' # 배열 길이
curl -s https://jsonplaceholder.typicode.com/users/1 | jq '.address.city' # 중첩 필드
curl -s https://jsonplaceholder.typicode.com/posts | jq first- jq '[.[] | {id, title}]' 로 id와 title만 추린 새 배열이 출력됐는가
- jq '.[] | select(.userId == 1)' 로 특정 사용자 게시물만 필터링됐는가
- jq 'length' 로 응답 배열의 항목 수가 숫자로 출력됐는가
OpenAPI 문서 읽기
OpenAPI(Swagger) 문서 — 실무에서 필요한 부분만
외부 결제 API 연동 담당을 맡았는데 문서 링크를 받았습니다. 문서가 100페이지가 넘습니다. 어디서부터 읽어야 할지 모르겠고, 테스트 환경과 운영 환경 URL이 다른데 어디에 있는지도 찾기 어렵습니다. OpenAPI 문서에는 일정한 구조가 있어서, 핵심 항목 6개만 찾을 줄 알면 100페이지를 다 읽지 않아도 curl 테스트를 시작할 수 있습니다.
외부 API 연동 전에 API 명세서를 읽어야 합니다. OpenAPI(구 Swagger) 문서는 표준화된 API 명세 형식입니다. 문서 전체를 다 읽을 필요는 없고, 연동에 필요한 핵심 정보만 빠르게 찾는 법을 익혀두는 것으로 충분합니다.
OpenAPI 문서에서 확인할 항목:
- BaseURL / Servers — API 기본 주소 (스테이징 vs 운영 URL 구분)
- Authentication — 인증 방식 (Bearer, API Key, OAuth)
- Endpoint — 메서드 + 경로 (
POST /v1/payments) - Parameters — 경로 파라미터, 쿼리 파라미터, 필수/선택 구분
- Request Body — 필드명, 타입, 필수 여부
- Responses — 성공(200/201) 응답 스키마, 에러 응답 형식
# Swagger UI가 없는 경우 — OpenAPI YAML/JSON 파일 직접 확인
# 실제 연동 전 curl로 직접 검증
# ① 인증 방식 확인 후 토큰 발급
curl -s -X POST -H "Content-Type: application/json" \
-d '{"client_id":"ID","client_secret":"SECRET"}' \
https://auth.example.com/token | jq .access_token
# ② 발급받은 토큰으로 실제 API 호출
TOKEN=$(curl -s -X POST ... | jq -r .access_token)
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/resource
트러블슈팅
원인: Bearer 토큰이 만료됐거나 Authorization 헤더 형식이 잘못됐습니다. 인프라 환경에서는 서버 시간 차이로 토큰이 일찍 만료되는 경우도 있습니다.
# ① 토큰 내용 확인 (JWT는 Base64 디코딩 가능)
TOKEN="eyJhbGci..."
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq .exp
# exp 값을 date 명령으로 변환해 만료 시각 확인
date -d @<exp값>
# ② Authorization 헤더 형식 확인 (Bearer 앞에 공백 주의)
curl -v -H "Authorization: Bearer $TOKEN" https://api.example.com/resource 2>&1 | grep "Authorization"
# ③ 토큰 갱신 (OAuth2 refresh_token 방식)
curl -s -X POST -H "Content-Type: application/json" \
-d '{"grant_type":"refresh_token","refresh_token":"REFRESH_TOKEN"}' \
https://auth.example.com/token
# ④ 서버 시간 동기화 확인 (토큰 유효성 시간 비교)
timedatectl status | grep "Local time"
원인: 요청에 Content-Type: application/json 헤더가 없거나, 잘못된 형식이 지정됐습니다. curl로 테스트할 때 가장 자주 빠뜨리는 헤더입니다.
# ① 현재 보내는 헤더 확인 (-v로 요청 헤더 출력)
curl -v -X POST -d '{"key":"value"}' https://api.example.com/resource 2>&1 | grep ">"
# Content-Type 줄이 없으면 헤더 누락
# ② Content-Type 헤더 추가
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"key":"value"}' \
https://api.example.com/resource
# ③ API가 application/x-www-form-urlencoded를 요구하는 경우
curl -s -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "key=value&key2=value2" \
https://api.example.com/resource
실제 업무에서 이 지식이 쓰이는 상황:
외부 API 장애 신고가 들어왔을 때, 인프라 엔지니어가 직접 curl로 해당 엔드포인트를 찔러보는 것이 가장 빠른 1차 진단입니다.
# 외부 API 장애 1차 진단 루틴
# 1. 기본 연결 확인 (DNS, 네트워크)
curl -s -o /dev/null -w "%{http_code}" https://api.paymentgateway.com/health
# 2. 인증 포함 실제 요청 테스트
curl -s -H "X-API-Key: $API_KEY" \
https://api.paymentgateway.com/v1/payments/test \
| jq '{status: .status, message: .message}'
# 3. 응답 시간 측정 (SLA 확인)
curl -s -o /dev/null \
-w "connect: %{time_connect}s | total: %{time_total}s\n" \
https://api.paymentgateway.com/v1/payments/test
# 4. 상태코드별 판단:
# 200/201 → API 정상, 앱 로직 문제일 가능성
# 401/403 → 인증 정보 만료 또는 IP 화이트리스트 문제
# 500/502 → 외부 API 서버 이상, 담당사에 연락
# timeout → 네트워크 구간 문제, 방화벽 확인
이 루틴을 외울 필요는 없습니다. curl 옵션 몇 가지만 익혀두면 API 연계 문제의 70%는 코드를 보기 전에 범위를 좁힐 수 있습니다. 다음 모듈에서는 이 API 호출 앞에 위치하는 API Gateway 구조와 Webhook 연계 패턴을 다룹니다.