← 기술 블로그

식약처 API 11종 연동기: 응답 구조가 4개나 되는 이유

Phase: 1 — 데이터 수집 시리즈: 공공데이터 레시피 대응 스토리: 운영 블로그 5화

이 글의 배경이 되는 이야기는 5화: 식약처 API 11종과의 첫 만남에서 읽을 수 있습니다.

이 글을 읽으면 알 수 있는 것


문제: 같은 질문, 다른 대답 형식

CheckEat은 식품 데이터를 수집하기 위해 두 곳의 정부 API를 사용합니다.

출처API 수데이터건수
식품안전나라 (foodsafetykorea.go.kr)9종제품, 원재료, 표준사전, 공전 규격~104만 건
공공데이터포털 (data.go.kr)3종영양성분, 원재료 정보, HACCP~150만 건

두 곳 모두 정부 기관이 운영하는 공공데이터 포털입니다. 같은 목적(식품 정보 제공)의 API인데, URL 구조, 응답 포맷, 에러 코드, 페이지네이션 방식 — 모든 것이 달랐습니다.

처음 API를 연결할 때는 “하나 해봤으니 나머지도 비슷하겠지”라고 생각했습니다. 틀렸습니다.


응답 구조 4가지 유형

11개 API의 응답을 분류하면 4가지 유형으로 나뉩니다.

유형 A: 식품안전나라 — 서비스명 래핑

9개 API가 이 구조를 따릅니다. 응답의 최상위 키가 서비스 코드명(I1250, C002 등)입니다.

{
  "I1250": {
    "RESULT": { "CODE": "INFO-000", "MSG": "정상 처리되었습니다" },
    "total_count": "1043709",
    "row": [{ "PRDLST_REPORT_NO": "197810010037", "PRDLST_NM": "..." }]
  }
}

여기서 이미 주의할 점이 여럿 있습니다:

유형 B: 공공데이터포털 — response 래핑

영양성분 API 등이 이 구조를 따릅니다. 유형 A와는 완전히 다른 형태입니다.

{
  "response": {
    "header": { "resultCode": "00", "resultMsg": "NORMAL SERVICE." },
    "body": {
      "totalCount": 284523,
      "items": [{ "FOOD_CD": "D018-001", "FOOD_NM_KR": "..." }]
    }
  }
}

유형 A와 비교하면:

유형 C: response 래핑이 빠진 변형

같은 공공데이터포털인데 response 래핑 없이 바로 headerbody가 나오는 API도 있습니다.

유형 D: 이중 중첩

HACCP API는 items 안에 item이 한 번 더 들어 있습니다. 같은 포털의 다른 API에서는 items가 바로 데이터 배열인데, 여기서는 한 겹 더 까야 합니다.


필드명 불일치: 같은 것의 다른 이름

응답 구조뿐 아니라, 같은 개념을 가리키는 필드명도 API마다 다릅니다.

“품목보고번호”는 한국에서 제조·수입 식품을 식별하는 유일한 키입니다. 이 하나의 개념이 API마다 이렇게 불립니다:

출처필드명표기법
식품안전나라PRDLST_REPORT_NO대문자_밑줄
공공데이터포털prdlstReportNo카멜케이스
영양성분 CSV품목제조보고번호한글

같은 번호인데 세 가지 이름으로 불리니, 데이터를 합치려면 “이 필드가 저 필드와 같은 것”이라고 매번 수동으로 지정해줘야 합니다.

에러 코드도 마찬가지입니다:

출처”성공""데이터 없음”
식품안전나라INFO-000INFO-200
data.go.kr A"00"items: null
data.go.kr B"OK"items: null

“성공했다”를 표현하는 방식조차 통일되어 있지 않습니다.


갈림길: 이 차이를 어떻게 흡수할 것인가

API를 하나씩 연결하면서 패턴이 보이기 시작했습니다. 식품안전나라 쪽 9개 API는 서로 구조가 같고, 공공데이터포털 쪽 3개도 서로 같습니다. 차이가 나는 것은 출처 사이이지, 같은 출처 안에서는 아닙니다.

그래서 세 가지 방법을 놓고 고민했습니다.

방법장점단점
API마다 개별 처리단순 명확재시도, 로깅, 중단점 복구 등 공통 로직이 11번 중복
하나의 범용 처리기코드 하나로 통일유형 A~D 분기가 뒤섞여서 오히려 복잡
출처별 처리기 2개공통 흐름 재사용 + 차이점만 분리초기 설계에 시간 필요

세 번째를 선택했습니다.

공통 흐름(재시도, 중단점 복구, 원본 저장, 작업 기록)은 한 곳에 구현하고, “API를 호출해서 응답을 파싱하는 부분”만 출처별로 나누는 구조입니다.

공통 처리기 (재시도, 중단점 복구, 원본 저장, 작업 기록)
├── 식품안전나라 전용 (유형 A 파싱) → 9개 API가 공유
└── 공공데이터포털 전용 (유형 B~D 파싱) → 3개 API가 공유

개별 API는 “어떤 API인지”와 “한 건의 데이터를 어떻게 저장하는지”만 정의하면 됩니다. 나머지 — 페이지를 넘기고, 실패하면 재시도하고, 어디까지 받았는지 기록하는 일 — 은 공통 처리기가 알아서 합니다.

이 구조 덕분에 새 API를 추가할 때마다 40~70줄 정도의 짧은 코드만 작성하면 되었습니다.


결과

항목수치
연동한 데이터 소스12종 (API 9 + CSV 1 + 고시문서 파싱 1 + HACCP 1)
응답 구조 유형4가지
필드명 케이싱3가지 (대문자_밑줄, 카멜케이스, 한글)
에러 코드 표현3가지 (INFO-000, "00", "OK")
총 적재 건수제품 104만 + 원재료 98만 + 영양 28만 + …

한계

공공 API의 일일 호출 한도에 도달하면, 현재는 일반 오류와 동일하게 처리됩니다. “한도를 넘었으니 내일 이어서 받으세요”라는 안내가 자동으로 나오지 않아서, 사용자가 에러 메시지를 직접 읽고 판단해야 합니다.

한 줄 교훈

“데이터가 공개되어 있다”와 “데이터를 쓸 수 있다” 사이에는 응답 구조 4종, 에러 코드 3종, 필드명 표기법 3종의 간극이 있습니다.


다음 글: INFO-200은 “성공”이 아닙니다 — 공공 API 에러 처리의 함정