API는 소프트웨어끼리 정해진 형식으로 주고받는 창구다. 요청하는 쪽은 상대의 내부를 몰라도, 약속된 형식대로 요청하면 결과를 돌려받는다. “API 연동”이라는 말은 흔한데 막상 API가 뭐냐고 물으면 설명이 길어진다. 나는 backend와 API를 만들고 그걸 iOS·Android 앱에서 불러 쓰는 일을 오래 했다. 개발자가 아닌 사람에게 설명할 때 가장 잘 통한 건 식당 비유였다.
식당으로 보면
식당에서 손님은 주방에 들어가 직접 요리하지 않는다. 메뉴판을 보고 점원에게 주문하면, 점원이 주방에 전하고 음식을 가져온다.
그림 1. 손님은 주방을 몰라도 메뉴판대로 점원에게 주문하면 원하는 걸 받는다.
| 식당 | 소프트웨어 | 역할 |
|---|---|---|
| 주방 | 서비스 내부 | 실제로 일이 처리되는 곳. 요청하는 쪽은 몰라도 된다 |
| 메뉴판 | 정해진 request 형식 | 무엇을 어떤 형식으로 요청할 수 있는지 정해 둔 목록 |
| 점원 | API | 주문을 받아 내부에 전하고, 완성된 결과를 돌려준다 |
손님(우리가 쓰는 앱)은 주방(서비스 내부)을 몰라도 점원(API)에게 메뉴판(정해진 형식)대로 주문하면 원하는 걸 받는다. API는 내부를 감추고 약속된 창구로만 주고받게 하는 점원이다.
만드는 쪽에서 이 구조가 고마운 이유는 주방을 바꿔도 손님이 모른다는 데 있다. DB를 갈아엎든 서버 구성을 바꾸든, 메뉴판과 응대 방식만 그대로면 앱은 손대지 않아도 돈다. mobile 앱에서는 이게 특히 크다. 이미 사용자 폰에 깔린 옛 버전 앱도 같은 API를 부르기 때문에, API 형식을 함부로 바꾸면 업데이트하지 않은 사용자부터 깨진다.
매일 쓰는 API
거창하게 들리지만 하루에도 수십 번 쓴다.
- 날씨 앱을 열면 앱이 기상 서비스 API에 “오늘 서울 날씨”를 요청하고 결과를 받아 온다.
- 지도 앱에서 길찾기를 누르면 지도 서비스 API에 경로를 묻는다.
- 결제할 때 카드 정보를 넣으면 앱이 결제사 API에 “이 금액 결제”를 요청한다.
메시지 전송, 음악 재생, 배달 상태 확인도 같다. 화면의 버튼 거의 하나하나 뒤에 점원이 한 명씩 서 있다.
그림 2. 버튼 하나를 누를 때마다 뒤에서는 API가 요청과 응답을 주고받는다.
sync: 주문하고 그 자리에서 받는다
대부분의 요청은 sync다. 날씨를 물으면 바로 답이 온다. client는 request를 보내고 response가 올 때까지 기다린다. 처리 시간이 짧은 일에는 이게 가장 단순하고 다루기 쉽다.
문제는 오래 걸리는 작업이다. 영상 인코딩, 대용량 파일 변환, 리포트 생성, 생성형 AI로 콘텐츠 만들기처럼 수십 초에서 몇 분이 걸리는 일을 sync로 붙잡고 있으면 connection이 timeout에 걸리고 앱 화면은 그동안 멈춰 있다. 모바일에서는 그 사이 네트워크가 바뀌거나 앱이 background로 내려가면서 연결이 끊기는 일도 잦다. 끊기면 서버는 일을 다 해 놓고도 결과를 돌려줄 곳을 잃는다.
async: 번호표를 먼저 받는다
그래서 무거운 작업은 async로 처리한다. 요리가 오래 걸리는 식당에서 진동벨을 받고 자리에서 기다리다가, 벨이 울리면 음식을 받으러 가는 것과 같다.
- 요청: client가 작업을 요청한다.
- 접수: 서버는 일을 다 끝내지 않고 바로 job id를 돌려준다. HTTP에서는 이때 “요청은 받았지만 처리는 아직 끝나지 않았다”는 뜻의
202 Accepted를 쓴다 [1]. - 상태 확인: client가 그 job id로 “어디까지 됐나”를 묻는다. 서버는 진행 중인지, 끝났는지를 알려 준다.
- 결과 수령: 끝났으면 결과를 받아 간다.
실제로 오가는 모양은 대략 이렇다.
POST /jobs -> 202 Accepted {"job_id": "a1b2", "status": "queued"}
GET /jobs/a1b2 -> 200 OK {"status": "running"}
GET /jobs/a1b2 -> 200 OK {"status": "done", "result_url": "..."}
이 흐름의 핵심은 2단계의 job id다. 요청과 결과가 하나의 connection에 묶이지 않으니, 중간에 연결이 끊겨도 id만 있으면 결과를 다시 찾아갈 수 있다.
async를 짤 때 내가 챙기는 것
- polling 간격. 상태 확인을 너무 촘촘히 하면 그 자체가 서버 부하가 된다. 처음엔 짧게, 갈수록 간격을 늘린다.
- 완료 알림. 상태를 계속 묻는 대신 끝났을 때 서버가 알려 주는 방식도 있다. 서버끼리는 webhook, 앱이면 push notification이다. 진동벨이 울리는 쪽에 더 가깝다.
- 중복 요청. 모바일은 재시도가 잦아서 같은 요청이 두 번 들어오기 쉽다. idempotency key를 받아 두면 같은 요청에 job이 두 개 생기지 않는다.
여기까지는 사람이 버튼을 누르고 진행을 확인하고 결과를 받는 이야기였다. 같은 주문을 사람 대신 AI가 넣게 하는 규약이 MCP이고, AI도 위의 네 단계를 그대로 밟는다.



