목차
개요
ROADMAX 파트너 연동 API 는 POS · 배달대행 · 앱 같은 외부 시스템이 ROADMAX 배달망에 배달을 접수하고, 접수한 콜을 조회하며, 상태가 바뀔 때마다 웹훅으로 알림을 받도록 하는 REST API 입니다.
- 견적 — 배달지까지의 안내 요금을 접수 전에 확인합니다.
- 접수 · 수정 · 취소 — 파트너 주문 번호와 함께 배달을 접수하고, 허용되는 상태에서 수정하거나 취소합니다.
- 조회 — 내가 접수한 콜의 상태와 배정된 라이더(가려진 정보)를 조회합니다.
- 웹훅 — 접수 · 배정 · 픽업 · 완료 · 취소 · 수정 이벤트를 내 서버로 받습니다.
- 샌드박스 — 실제 배달과 분리된 환경에서 전체 흐름을 먼저 시험합니다.
| 항목 | 내용 |
|---|---|
| 기본 URL | https://roadmax.kr/api/v1/partner/v1 |
| 형식 | JSON(Content-Type: application/json), UTF-8 |
| 시각 | ISO 8601, UTC — 예: 2026-10-02T01:23:45.000Z |
| 금액 | 원(KRW) 단위 정수 — 예: 4300 |
| API 버전 | 응답 헤더 X-RoadMax-Api-Version: 2026-10-02 |
| 요청 ID | 응답 헤더 X-RoadMax-Request-Id — 문의할 때 함께 알려 주세요 |
기계가 읽는 명세와 시험 화면도 제공합니다: OpenAPI JSON (새 탭) · Swagger UI (새 탭)
인증
모든 요청에 X-RoadMax-Api-Key 헤더로 API 키를 보냅니다.
X-RoadMax-Api-Key: rmk_<test|live>_<32자>- 키는 운영 담당이 발급합니다. 발급 화면에서 한 번만 보이며, 분실하면 재발급을 요청해야 합니다.
- 샌드박스 키
rmk_test_…와 라이브 키rmk_live_…는 데이터가 완전히 분리됩니다. 샌드박스 키로는 샌드박스 콜만, 라이브 키로는 라이브 콜만 다룹니다. - 키는 서버에서만 사용하세요. 앱이나 웹 화면(클라이언트 코드)에 넣으면 누구나 볼 수 있습니다.
스코프
키마다 호출할 수 있는 범위(스코프)가 정해져 있습니다. 스코프를 따로 요청하지 않으면 기본 스코프(rider_location:read 제외)가 부여되고, 라이더 위치 조회는 필요할 때 운영 담당에게 따로 요청합니다. 범위를 벗어나면 403 insufficient_scope 를 받습니다.
| 스코프 | 허용되는 일 | 해당 엔드포인트 |
|---|---|---|
stores:read | 매장 매핑 조회 | GET /stores |
quotes:create | 견적 요청 | POST /quotes |
deliveries:read | 콜 조회 | GET /deliveries GET /deliveries/:id |
deliveries:write | 콜 접수 · 수정 · 취소 · 샌드박스 진행 | POST /deliveries PATCH /deliveries/:id POST /deliveries/:id/cancel POST /sandbox/deliveries/:id/advance |
rider_location:read | 라이더 위치 조회(옵션 기능) | GET /deliveries/:id/rider-location |
webhooks:test | 웹훅 시험 발송 | POST /webhooks/test |
curl -s "https://roadmax.kr/api/v1/partner/v1/stores" \
-H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"샌드박스 시작하기
아래 순서대로 따라 하면 접수부터 완료까지 한 번에 확인할 수 있습니다. 예제의 키와 시크릿은 모두 가짜 값이니 발급받은 값으로 바꿔서 실행하세요.
- 운영 담당에게 요청합니다
다음 네 가지를 한 번에 요청하면 빠릅니다.
- 파트너 등록
- 샌드박스 API 키 발급
- 시험 매장 매핑 (파트너 매장 코드와 시험 상점 연결)
- 웹훅 수신 URL 등록 (서명 시크릿
whsec_…발급)
키와 시크릿은 발급 화면에서 한 번만 보입니다. 받는 즉시 안전한 곳에 보관하세요.
GET /stores로 매핑을 확인합니다운영 담당이 등록한 파트너 매장 코드가 보이면 인증과 매핑이 정상입니다.
bashcurl -s "https://roadmax.kr/api/v1/partner/v1/stores" \ -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"POST /quotes로 견적을 받습니다배달지 좌표(또는 주소)를 보내면 안내 요금이 내려옵니다. 응답 예제는 4장 견적을 참고하세요.
bashcurl -s -X POST "https://roadmax.kr/api/v1/partner/v1/quotes" \ -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "partnerStoreCode": "STORE-001", "dropoff": { "lat": 36.9288, "lng": 127.6867, "address": "충북 음성군 맹동면 혁신로 10" } }'POST /webhooks/test로 서명 검증 코드를 확인합니다등록한 웹훅 URL 로 샘플 이벤트(본문에
data.sample: true)가 즉시 전송됩니다. 5장 서명 검증의 예제 코드로 검증이 통과하는지 확인하세요.bashcurl -s -X POST "https://roadmax.kr/api/v1/partner/v1/webhooks/test" \ -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "eventType": "delivery.assigned" }'POST /deliveries로 접수합니다Idempotency-Key헤더는 필수입니다. 응답 예제는 4장 접수를 참고하세요.bashcurl -s -X POST "https://roadmax.kr/api/v1/partner/v1/deliveries" \ -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \ -H "Idempotency-Key: order-20261002-0001" \ -H "Content-Type: application/json" \ -d '{ "partnerStoreCode": "STORE-001", "partnerRef": "ORDER-20261002-0001", "dropoff": { "address": "충북 음성군 맹동면 혁신로 20", "detail": "101동 1203호", "phone": "010-1234-5678", "lat": 36.9301, "lng": 127.689 }, "itemTotal": 15000, "deliveryFee": 4300, "memo": "문 앞에 놓아주세요" }'POST /sandbox/deliveries/:id/advance로 콜을 진행시킵니다배정 → 픽업 → 완료 순서로 진행시키며 각 단계의 웹훅(
delivery.assigned,delivery.picked_up,delivery.delivered)이 도착하는지 확인합니다. 샌드박스 키 전용입니다.bashcurl -s -X POST "https://roadmax.kr/api/v1/partner/v1/sandbox/deliveries/cm1x2y3z40000abcd1234efgh/advance" \ -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"- 라이브 키로 바꾸기 전 체크리스트
- 웹훅 서명 검증을 구현했고, 잘못된 서명과 오래된 타임스탬프를 거절하는지 확인했다.
- 같은 이벤트가 다시 와도 한 번만 처리한다 (이벤트
id로 중복 제거). - 접수 요청마다
Idempotency-Key를 보내고, 응답을 받지 못했을 때는 같은 키로 다시 요청한다. - 웹훅 수신은 2xx 를 빨리 돌려주고, 재시도 · 소진(DEAD) · 일시 중지(PAUSED) 규칙을 이해했다.
- 429
rate_limited를 받으면Retry-After만큼 기다렸다가 재시도한다.
위 항목을 확인한 뒤 운영 담당에게 라이브 키 발급을 요청하세요. 라이브 키는 실제 배달 접수에 쓰이므로 샌드박스에서 먼저 충분히 시험해야 합니다.
접수 흐름
콜(배달 한 건)은 보통 다음 순서로 진행됩니다.
- 견적 (선택)
POST /quotes로 배달지까지의 안내 요금을 확인합니다. - 접수
POST /deliveries로 배달을 접수합니다.Idempotency-Key헤더가 필수이며, 요금을 생략하면 견적 금액으로 접수됩니다. - 상태 확인
상태가 바뀔 때마다 웹훅으로 알림을 받는 방식을 권장합니다. 필요하면
GET /deliveries/:id로 직접 조회할 수도 있습니다. - 수정 · 취소 (필요할 때)
PATCH /deliveries/:id로 수정하거나POST /deliveries/:id/cancel로 취소합니다. 허용되는 상태에서만 가능합니다.
엔드포인트 한눈에 보기
모든 경로는 기본 URL(https://roadmax.kr/api/v1/partner/v1) 뒤에 붙습니다. 아래 요청 예제는 HTTP 원문 형식이며, 키는 가짜 값입니다.
| 메서드 | 경로 | 설명 | 필요 스코프 |
|---|---|---|---|
| GET | /stores | 내 매장 매핑 목록 — 파트너 매장 코드, 상점 이름·주소, 상태 | stores:read |
| POST | /quotes | 배달 요금 견적 — 안내 금액이며 접수 시점에 확정됩니다 | quotes:create |
| POST | /deliveries | 배달 접수 — Idempotency-Key 헤더 필수, 성공하면 201 | deliveries:write |
| GET | /deliveries/:id | 콜 단건 조회 | deliveries:read |
| GET | /deliveries | 콜 목록 — from, to, status, partnerRef, cursor, limit | deliveries:read |
| PATCH | /deliveries/:id | 배달지 주소(상세 · 좌표) · 배달비 · 메모 수정 — 픽업 전(pending · assigned)의 콜만 | deliveries:write |
| POST | /deliveries/:id/cancel | 콜 취소 — 배차 전(pending)의 콜만(배차 뒤는 409, 지점에 문의), 본문 { "reason": "..." } 은 선택 | deliveries:write |
| GET | /deliveries/:id/rider-location | 라이더 위치(옵션) — 기본 비활성, 꺼져 있으면 403 feature_disabled | rider_location:read |
| POST | /webhooks/test | 샘플 웹훅 즉시 발송 | webhooks:test |
| POST | /sandbox/deliveries/:id/advance | 샌드박스 콜을 다음 단계로 진행 — 샌드박스 키 전용 | deliveries:write |
매장 조회 — GET /stores
운영 담당이 등록한 매핑만 보입니다. status 가 ACTIVE 가 아닌 매장 코드로 견적이나 접수를 보내면 404 store_not_mapped 로 거절됩니다.
GET https://roadmax.kr/api/v1/partner/v1/stores
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX{
"data": [
{
"partnerStoreCode": "STORE-001",
"storeName": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"status": "ACTIVE"
}
]
}견적 — POST /quotes
접수 전에 안내 요금을 확인합니다. dropoff 에는 좌표(lat, lng)나 주소 중 하나 이상이 필요하고, pickup 을 생략하면 매핑된 상점의 위치를 씁니다.
POST https://roadmax.kr/api/v1/partner/v1/quotes
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/json
{
"partnerStoreCode": "STORE-001",
"dropoff": {
"lat": 36.9288,
"lng": 127.6867,
"address": "충북 음성군 맹동면 혁신로 10"
}
}{
"data": {
"fee": 4300,
"distanceKm": 2.31,
"currency": "KRW",
"breakdown": {
"base": 3800,
"extra": 500,
"cap": 10000
},
"partnerStoreCode": "STORE-001",
"estimate": true
}
}fee— 안내 요금(원),distanceKm— 거리(km)breakdown— 요금 산출 내역 (base기본 요금,extra추가 요금,cap상한)estimate: true— 안내 금액이며 접수 시점에 확정됩니다.
이 매장을 담당하는 지점이 접수를 받지 않는 상태이거나 위치를 확인할 수 없으면 422 unprocessable 로 거절됩니다. 사유와 대상 항목은 details (예: reason, field)에 담겨 내려옵니다. 매핑되지 않은 매장 코드는 404 store_not_mapped 입니다.
접수 — POST /deliveries
Idempotency-Key 헤더가 필수입니다(6장 멱등). deliveryFee 를 생략하면 픽업 · 배달 좌표로 거리 요금표를 적용해 접수합니다 (배달지 좌표를 보내고 상점 위치가 등록돼 있어야 하며, 계산할 수 없으면 400 입니다). 샌드박스 키로 접수한 콜은 시험 데이터로 만들어지고 실제 배달로 이어지지 않습니다.
POST https://roadmax.kr/api/v1/partner/v1/deliveries
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Idempotency-Key: order-20261002-0001
Content-Type: application/json
{
"partnerStoreCode": "STORE-001",
"partnerRef": "ORDER-20261002-0001",
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20",
"detail": "101동 1203호",
"phone": "010-1234-5678",
"lat": 36.9301,
"lng": 127.689
},
"itemTotal": 15000,
"deliveryFee": 4300,
"memo": "문 앞에 놓아주세요"
}| 필드 | 설명 |
|---|---|
partnerStoreCode | 필수. 운영 담당이 매핑한 파트너 매장 코드(64자 이내) |
partnerRef | 파트너 쪽 주문 식별자(100자 이내). 콜 조회(partnerRef 필터)와 응답·웹훅에서 같은 값으로 내려옵니다. 같은 환경에서 취소되지 않은 콜과 겹치면 409 conflict 이고, 취소된 번호는 다시 쓸 수 있습니다. |
dropoff | 필수. 배달지 — address(주소), detail(상세 주소, 있으면 "주소 상세" 로 이어 붙어 라이더에게 보임), name(수령인), phone(고객 연락처), lat · lng(좌표, 한국 범위·함께 보내기). 주소 또는 좌표 중 하나는 있어야 합니다. |
pickup | 선택. 픽업지 — name, address, phone, lat · lng. 생략하면 매핑된 상점의 정보를 씁니다. |
itemTotal | 상품 금액(원, 정수) |
totalAmount | 결제 금액(원, 정수) |
deliveryFee | 배달 요금(원, 1 이상 정수). 생략하면 거리 요금표로 계산한 금액으로 접수됩니다. 0원 · 음수는 400, 본사가 정한 배달비 하한이 있으면 그 미만은 422 입니다. |
payment | 결제 수단 표시(자유 문자열, 50자 이내) |
memo | 배달 요청 사항(300자 이내) |
externalChannel | 주문을 받은 외부 채널 표시: 배민 · 요기요 · 쿠팡이츠 · 전화 · 기타 |
externalOrderRef | 외부 채널의 주문번호(100자 이내). 응답·웹훅의 콜 객체에 같은 값으로 내려옵니다. |
{
"data": {
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "pending",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20 101동 1203호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "문 앞에 놓아주세요",
"fee": {
"deliveryFee": 4300,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": null,
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": null,
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": null,
"updatedAt": "2026-10-02T01:23:45.000Z",
"sandbox": false
},
"estimatedFee": 4300
}data— 콜 객체. 조회 · 웹훅과 같은 모양입니다.estimatedFee— 거리 요금표로 계산한 안내 금액(원). 픽업 · 배달 좌표가 모두 있을 때만 값이 있고, 보낸deliveryFee와 다를 수 있습니다(없으면null).
같은 Idempotency-Key 로 다시 요청하면 첫 응답이 그대로 재생됩니다. 네트워크 오류로 응답을 받지 못했다면 같은 키로 다시 요청하세요.
접수가 거절되는 경우
| HTTP | code | 사유 |
|---|---|---|
| 400 | invalid_request | 값 형식 오류, 배달지 주소 · 좌표가 모두 없음, 배달비를 계산할 좌표가 없음 등 (details 확인) |
| 404 | store_not_mapped | 매핑되지 않았거나 중지된 매장 코드 |
| 409 | conflict | 같은 partnerRef 의 취소되지 않은 콜이 이미 있음 (details.deliveryId 가 기존 콜) |
| 409 | branch_min_riders | 담당 지점의 배차 가능 라이더가 부족해 지금은 접수할 수 없음. 잠시 뒤 다시 시도하세요. |
| 422 | unprocessable | details.reason 로 구분: branch_unavailable(담당 지점이 접수를 받지 않음) · store_inactive(매장 운영 중지) · store_cash_insufficient · store_minus_outstanding · store_minus_cap_exceeded(매장의 캐시 잔액 부족 · 미정산) · delivery_fee_below_min(배달비가 하한 미만, details.min 이 하한) — 좌표 오류는 details.field |
{
"error": {
"code": "branch_min_riders",
"message": "현재 배차 가능한 라이더가 부족합니다",
"requestId": "req_3f9a1c7d2b8e4a6f10c5d9e2"
}
}매장 캐시 · 미정산으로 거절되면 매장(사장님)이 캐시를 충전한 뒤에 다시 접수할 수 있습니다. 응답에 매장의 잔액 금액은 담기지 않습니다. 샌드박스 접수에는 이 검사가 없습니다.
콜 객체와 상태
조회 · 접수 · 수정 · 취소 · 샌드박스 진행의 응답 data 와 웹훅의 data.delivery 는 모두 같은 모양의 콜 객체입니다.
{
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "assigned",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20 101동 1203호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "문 앞에 놓아주세요",
"fee": {
"deliveryFee": 4300,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": {
"name": "김**",
"phoneMasked": "010-5678-****"
},
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": "2026-10-02T01:24:10.000Z",
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": null,
"updatedAt": "2026-10-02T01:24:10.000Z",
"sandbox": false
}| 필드 | 설명 |
|---|---|
id | 콜 ID |
partnerRef | 접수 때 보낸 파트너 주문 식별자 |
partnerStoreCode | 파트너 매장 코드 |
status | 콜 상태 (아래 표) |
externalChannel | 외부 채널 값(배민 · 요기요 · 쿠팡이츠 · 전화 · 기타). 해당 없으면 null |
externalOrderRef | 접수 때 보낸 외부 채널의 주문번호. 해당 없으면 null |
pickup | 픽업(상점) 이름 · 주소 · 좌표 |
dropoff | 배달지 주소 · 좌표. 접수 때 상세 주소(detail)를 보냈다면 주소 뒤에 이어 붙은 값입니다. |
customerPhoneMasked | 끝 4자리가 가려진 고객 연락처 |
fee | 배달 요금 · 상품 금액(원) · 통화 |
rider | 배정된 라이더(배정 뒤 완료 전까지와 완료 뒤). 배정 전이거나 취소된 콜은 null |
createdAt … updatedAt | 접수 · 배정 · 픽업 · 완료 · 취소 · 갱신 시각. 아직 일어나지 않은 단계는 null |
sandbox | 샌드박스 콜이면 true |
상태 값
| 값 | 의미 |
|---|---|
pending | 접수됨 · 배정 대기 |
assigned | 라이더 배정 |
picked_up | 픽업 완료 |
in_transit | 이동 중 |
delivered | 배달 완료 |
cancelled | 취소 |
- 고객 전화번호는 항상 끝 4자리를 가려서 내려갑니다 (
customerPhoneMasked). - 라이더 정보(
rider)는 배정된 뒤에만 내려가며, 성+별표 이름과 끝 4자리가 가려진 연락처만 담깁니다. 배정 전에는null입니다. - 취소 사유 같은 자유 텍스트는 응답과 웹훅 어디에도 내려가지 않습니다.
조회 — GET /deliveries/:id · GET /deliveries
단건 조회
응답은 위의 { "data": <콜 객체> } 입니다. 내 콜이 아니거나 없는 ID 이면 404 not_found 입니다.
GET https://roadmax.kr/api/v1/partner/v1/deliveries/cm1x2y3z40000abcd1234efgh
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX목록 조회
| 파라미터 | 설명 |
|---|---|
from | 조회 시작(포함), ISO 8601 |
to | 조회 끝(미포함), ISO 8601 |
status | 상태 값 하나 (pending, assigned, picked_up, in_transit, delivered, cancelled) |
partnerRef | 파트너 주문 번호 (정확히 일치) |
cursor | 이전 응답의 nextCursor |
limit | 한 번에 가져올 개수 (기본 50, 최대 100) |
curl -s "https://roadmax.kr/api/v1/partner/v1/deliveries?from=2026-10-02T00:00:00%2B09:00&status=assigned&limit=20" \
-H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"{
"data": [
{
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "assigned",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20 101동 1203호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "문 앞에 놓아주세요",
"fee": {
"deliveryFee": 4300,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": {
"name": "김**",
"phoneMasked": "010-5678-****"
},
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": "2026-10-02T01:24:10.000Z",
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": null,
"updatedAt": "2026-10-02T01:24:10.000Z",
"sandbox": false
}
],
"nextCursor": "MTc5...",
"hasMore": true
}접수 시각 내림차순으로 내려옵니다. hasMore 가 true 이면 nextCursor 를 cursor 에 넣어 다음 페이지를 가져옵니다. 내가 접수한 콜만 보이고, 샌드박스 키는 샌드박스 콜만 보입니다.
수정과 취소
수정 — PATCH /deliveries/:id
배달지 주소 · 배달비 · 메모를 바꿉니다. 픽업 전(pending · assigned)의 콜만 수정할 수 있고, 픽업 이후 · 완료 · 취소된 콜은 409 conflict 로 거절됩니다. 접수 요청과 같은 필드 이름으로 바꿀 항목만 보내세요(하나도 없으면 400).
| 필드 | 설명 |
|---|---|
dropoff.address | 새 배달지 주소. 라이더에게 보이는 주소도 함께 바뀝니다. 좌표 없이 주소만 보내면 서버가 주소로 좌표를 다시 찾습니다(찾지 못하면 기존 좌표 유지). |
dropoff.detail | 새 상세 주소. address 와 함께 보내며 "주소 상세" 로 이어 붙습니다. |
dropoff.lat · dropoff.lng | 새 배달지 좌표(한국 범위). 서로 함께, 그리고 address 와 함께 보냅니다. 보내면 서버가 다시 찾은 좌표보다 이 값이 우선합니다. |
deliveryFee | 새 배달비(원, 1 이상 100,000 이하 정수 — 0원 · 음수는 400, 배달비 하한 미만은 422 delivery_fee_below_min). 배정 전(pending)에는 바로 바뀌고, 배정 뒤(assigned)에는 라이더가 동의하기 전까지 보류됩니다. |
memo | 새 요청 사항. 빈 문자열이면 메모를 지웁니다. |
PATCH https://roadmax.kr/api/v1/partner/v1/deliveries/cm1x2y3z40000abcd1234efgh
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/json
{
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 22",
"detail": "102동 501호"
},
"deliveryFee": 4800,
"memo": "벨을 눌러 주세요"
}{
"data": {
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "pending",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 22 102동 501호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "벨을 눌러 주세요",
"fee": {
"deliveryFee": 4800,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": null,
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": null,
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": null,
"updatedAt": "2026-10-02T01:25:30.000Z",
"sandbox": false
},
"editedFields": [
"address",
"memo",
"deliveryFee"
],
"feeChangePending": false
}data— 수정된 콜 객체editedFields— 바뀐(또는 요청된) 항목. 웹훅delivery.edited의data.edit.field와 같은 이름입니다(address·memo·deliveryFee·dropoffCoords).feeChangePending— 배정 뒤 배달비 변경이 라이더 동의를 기다리는 중이면true(이때fee.deliveryFee는 아직 그대로입니다)
취소 — POST /deliveries/:id/cancel
배차 전(pending)의 콜만 취소할 수 있습니다. 라이더가 배정된 뒤(assigned 부터, 픽업 이후 포함)에는 409 conflict("배차 뒤 취소는 지점에 문의해 주세요.")이며, 배차 뒤 취소는 담당 지점에 문의하세요(지점이 직접 취소합니다). 이미 취소된 콜도 409 conflict 입니다. 본문 { "reason": "..." } 은 선택입니다(300자 이내, 생략하면 "사유 미기재"로 기록). 사유 같은 자유 텍스트는 응답이나 웹훅에는 내려가지 않습니다. 취소하면 담당 지점에 알림이 가고, 응답은 취소된 { "data": <콜 객체> } 입니다.
POST https://roadmax.kr/api/v1/partner/v1/deliveries/cm1x2y3z40000abcd1234efgh/cancel
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/json
{
"reason": "고객 요청으로 취소"
}{
"data": {
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "cancelled",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20 101동 1203호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "문 앞에 놓아주세요",
"fee": {
"deliveryFee": 4300,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": null,
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": "2026-10-02T01:24:10.000Z",
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": "2026-10-02T01:30:05.000Z",
"updatedAt": "2026-10-02T01:30:05.000Z",
"sandbox": false
}
}라이더 위치(옵션) — GET /deliveries/:id/rider-location
배정된 라이더의 최근 위치(좌표와 갱신 시각)만 돌려줍니다. 기본으로 꺼져 있는 옵션 기능이라 전역 설정과 이 파트너의 웹훅 엔드포인트 설정이 모두 켜져 있어야 하고, 하나라도 꺼져 있으면 403 feature_disabled 를 받습니다. 사용하려면 운영 담당에게 요청하세요(키에 rider_location:read 스코프도 필요합니다). 웹훅의 rider.location 이벤트도 기본 꺼짐입니다.
GET https://roadmax.kr/api/v1/partner/v1/deliveries/cm1x2y3z40000abcd1234efgh/rider-location
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX{
"data": {
"deliveryId": "cm1x2y3z40000abcd1234efgh",
"status": "assigned",
"location": {
"lat": 36.9291,
"lng": 127.6872,
"updatedAt": "2026-10-02T01:26:40.000Z"
}
}
}{
"data": {
"deliveryId": "cm1x2y3z40000abcd1234efgh",
"status": "pending",
"location": null
}
}location은 콜이 배정된 뒤 완료되기 전(assigned·picked_up·in_transit)이고 최근 5분 안에 갱신된 위치일 때만 값이 있습니다. 배정 전 · 완료 · 취소된 콜과 오래된 위치는null입니다.updatedAt은 라이더 위치가 마지막으로 갱신된 시각입니다. 라이더 이름 · 연락처 같은 식별 정보는 이 응답에 없습니다.- 내 콜이 아니거나 없는 ID 는 404
not_found입니다.
{
"error": {
"code": "feature_disabled",
"message": "라이더 위치 공유는 아직 사용할 수 없습니다. 운영 담당에게 문의해 주세요.",
"requestId": "req_3f9a1c7d2b8e4a6f10c5d9e2"
}
}웹훅 시험과 샌드박스 진행
POST /webhooks/test
등록된 웹훅 URL(같은 환경)로 샘플 이벤트를 즉시 보내고 결과를 돌려줍니다. eventType 을 생략하면 delivery.created 입니다. 특정 엔드포인트만 시험하려면 endpointId 를 함께 보내세요. 재시도나 상태 변경은 없고, 등록된 엔드포인트가 없으면 422 unprocessable 입니다.
POST https://roadmax.kr/api/v1/partner/v1/webhooks/test
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/json
{
"eventType": "delivery.assigned"
}{
"data": [
{
"endpointId": "cm1x2y3z40001wxyz5678abcd",
"eventId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"eventType": "delivery.assigned",
"delivered": true,
"statusCode": 200,
"latencyMs": 124,
"error": null
}
]
}delivered— 수신 서버가 2xx 로 응답했는지statusCode·latencyMs— 수신 서버의 응답 코드와 걸린 시간(ms)error— 실패했을 때의 요약 (성공이면 null)
POST /sandbox/deliveries/:id/advance
샌드박스 콜을 한 번 호출할 때마다 다음 단계로 한 칸 진행시킵니다: 접수(pending) → 배정(assigned, 샌드박스 라이더) → 픽업(picked_up) → 완료(delivered).샌드박스 키 전용이며, 라이브 키로 호출하면 403 sandbox_only 를 받습니다. 이미 완료 · 취소된 콜은 더 진행할 수 없어 409 conflict 입니다. 단계마다 해당 웹훅(delivery.assigned · delivery.picked_up · delivery.delivered)이 발송되어 웹훅 왕복을 시험할 수 있습니다.
POST https://roadmax.kr/api/v1/partner/v1/sandbox/deliveries/cm1x2y3z40000abcd1234efgh/advance
X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX{
"data": {
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "assigned",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20 101동 1203호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "문 앞에 놓아주세요",
"fee": {
"deliveryFee": 4300,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": {
"name": "김**",
"phoneMasked": "010-5678-****"
},
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": "2026-10-02T01:24:10.000Z",
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": null,
"updatedAt": "2026-10-02T01:24:10.000Z",
"sandbox": false
},
"from": "pending",
"to": "assigned"
}data— 진행된 콜 객체from·to— 진행 전 · 후 상태
웹훅
콜의 상태가 바뀌면 등록한 웹훅 URL 로 POST 요청이 옵니다. 웹훅 URL 은 운영 담당이 등록하며, 이때 발급되는 서명 시크릿(whsec_…)은 한 번만 보입니다. 폴링 없이 상태 변화를 바로 받을 수 있으니, 접수한 콜의 진행 상황은 웹훅으로 받는 방식을 권장합니다.
이벤트와 본문
| 이벤트 | 발생 시점 |
|---|---|
delivery.created | 접수 |
delivery.assigned | 배정 — 재배정이면 다시 발생합니다 |
delivery.picked_up | 픽업 |
delivery.delivered | 완료 |
delivery.cancelled | 취소 |
delivery.edited | 수정 — data.edit 이 함께 옵니다 |
rider.location | 라이더 위치 (옵션, 기본 꺼짐) — 배정된 뒤 완료되기 전, 위치가 갱신되면 최대 30초 간격으로 옵니다. 전역 설정과 엔드포인트의 위치 공유 설정이 켜져 있고 구독 이벤트(events)에 rider.location 을 넣은 엔드포인트에만 나갑니다. |
모든 이벤트는 같은 모양의 본문(봉투)으로 옵니다.
{
"id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"object": "event",
"type": "delivery.assigned",
"apiVersion": "2026-10-02",
"createdAt": "2026-10-02T01:24:10.000Z",
"livemode": true,
"data": {
"delivery": {
"id": "cm1x2y3z40000abcd1234efgh",
"partnerRef": "ORDER-20261002-0001",
"partnerStoreCode": "STORE-001",
"status": "assigned",
"externalChannel": null,
"externalOrderRef": null,
"pickup": {
"name": "쌀통닭 맹동점",
"address": "충북 음성군 맹동면 혁신로 10",
"lat": 36.9288,
"lng": 127.6867
},
"dropoff": {
"address": "충북 음성군 맹동면 혁신로 20 101동 1203호",
"lat": 36.9301,
"lng": 127.689
},
"customerPhoneMasked": "010-1234-****",
"memo": "문 앞에 놓아주세요",
"fee": {
"deliveryFee": 4300,
"itemTotal": 15000,
"currency": "KRW"
},
"rider": {
"name": "김**",
"phoneMasked": "010-5678-****"
},
"createdAt": "2026-10-02T01:23:45.000Z",
"assignedAt": "2026-10-02T01:24:10.000Z",
"pickedUpAt": null,
"deliveredAt": null,
"cancelledAt": null,
"updatedAt": "2026-10-02T01:24:10.000Z",
"sandbox": false
}
}
}| 필드 | 설명 |
|---|---|
id | 이벤트 ID(UUID). 같은 이벤트는 다시 보내도 같은 id 입니다. |
object | 항상 event |
type | 이벤트 종류 (위 표) |
apiVersion | API 버전 날짜 |
createdAt | 이벤트가 발생한 시각 (ISO 8601, UTC) |
livemode | 라이브 콜이면 true, 샌드박스 콜이면 false |
data.delivery | 이벤트를 만든 시점의 최신 콜 객체. 짧은 시간에 여러 단계가 지나가면 이벤트 종류와 status 가 다를 수 있습니다(예: delivery.created 인데 status 가 assigned). 재시도할 때도 같은 본문을 다시 보냅니다. |
data.edit | delivery.edited 에만 포함 — field(수정된 항목), pending(수정이 대기 중인 요청인지 여부), actorRole(수정한 쪽의 역할) |
data.location | rider.location 에만 포함 — lat, lng, at 만 담기며 라이더 식별 정보는 없습니다 |
data.sample | POST /webhooks/test 의 샘플 이벤트에만 true |
{
"edit": {
"field": "memo",
"pending": false,
"actorRole": "OWNER"
}
}{
"location": {
"lat": 36.9291,
"lng": 127.6872,
"at": "2026-10-02T01:26:40.000Z"
}
}- 순서는 보장되지 않습니다. 이벤트가 늦게 도착할 수 있으니 도착 순서가 아니라
createdAt과data.delivery.status로 최신 상태를 판단하세요. 이벤트 종류는 "무슨 일이 있었는지"를,status는 "지금 상태"를 알려 줍니다. - 같은 이벤트가 중복으로 올 수 있습니다. 이벤트
id를 기록해 두었다가 이미 처리한 id 는 건너뛰세요.
요청 헤더
| 헤더 | 값 |
|---|---|
Content-Type | application/json; charset=utf-8 |
User-Agent | RoadMax-Webhook/1.0 |
X-RoadMax-Signature | 서명 — t=<unix초>,v1=<hex> (아래 서명 검증) |
X-RoadMax-Event-Id | 이벤트 ID |
X-RoadMax-Event-Type | 이벤트 종류 |
X-RoadMax-Api-Version | API 버전 날짜 |
X-RoadMax-Delivery-Attempt | 이번이 몇 번째 시도인지 |
서명 검증
웹훅이 ROADMAX 에서 온 것인지, 중간에 바뀌지 않았는지 확인하려면 반드시 서명을 검증하세요.
| 항목 | 규칙 |
|---|---|
| 헤더 | X-RoadMax-Signature: t=<unix초>,v1=<hex> |
| 서명 대상 문자열 | "<t>.<받은 본문 원문 그대로>" |
| 알고리즘 | HMAC-SHA256 |
| 키 | 엔드포인트 등록 때 한 번 받은 시크릿 (whsec_…) |
| 결과 | 소문자 hex |
- JSON 으로 파싱하기 전의 원문 바이트로 검증하세요. 파싱한 뒤 다시 직렬화하면 서명이 맞지 않습니다.
- 타임스탬프
t가 현재 시각과 5분(300초) 넘게 차이 나면 거절하세요 (재생 공격 방지). - 서명 비교는 상수 시간 비교(
timingSafeEqual/compare_digest)로 하세요. - 시크릿을 교체하면 교체 직후부터 새 시크릿으로 서명됩니다(운영 담당이 새 시크릿을 알려 줄 때 수신 서버의 시크릿도 바로 바꾸세요). 앞으로 교체 유예를 지원하면
v1이 둘 이상 올 수 있으니, 하나라도 맞으면 통과시키는 방식으로 구현해 두세요. - 시도마다 새 타임스탬프로 다시 서명하므로, 재시도된 이벤트도 같은 방법으로 검증하면 됩니다.
export ROADMAX_WEBHOOK_SECRET="whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"// Node.js (Express) — 서명 검증 예제
const crypto = require('crypto');
const express = require('express');
const app = express();
// 서명은 "받은 원문 그대로"에 대해 계산됩니다. JSON 으로 파싱하기 전의 raw body 를 쓰세요.
app.post('/roadmax/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.ROADMAX_WEBHOOK_SECRET; // whsec_...
const header = req.get('X-RoadMax-Signature') || ''; // t=1790000000,v1=ab12...
const parts = header.split(',').map((p) => p.split('='));
const t = Number((parts.find(([k]) => k === 't') || [])[1]);
const candidates = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.status(400).send('timestamp');
const raw = req.body.toString('utf8');
const expected = Buffer.from(crypto.createHmac('sha256', secret).update(`${t}.${raw}`).digest('hex'));
const ok = candidates.some((v) => {
const given = Buffer.from(v || '');
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
if (!ok) return res.status(400).send('signature');
const event = JSON.parse(raw);
// event.id 로 이미 처리한 이벤트인지 확인한 뒤 처리하세요(같은 이벤트가 다시 올 수 있습니다).
res.sendStatus(200); // 2xx 를 빨리 돌려주세요. 오래 걸리는 일은 큐로 넘기세요.
});# Python (Flask) — 서명 검증 예제
import hashlib
import hmac
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["ROADMAX_WEBHOOK_SECRET"].encode() # whsec_...
@app.post("/roadmax/webhook")
def webhook():
raw = request.get_data() # 파싱하기 전의 원문 바이트
header = request.headers.get("X-RoadMax-Signature", "") # t=1790000000,v1=ab12...
pairs = [p.split("=", 1) for p in header.split(",") if "=" in p]
try:
t = int(next((v for k, v in pairs if k == "t"), "0"))
except ValueError:
t = 0
candidates = [v for k, v in pairs if k == "v1"]
if not t or abs(time.time() - t) > 300:
abort(400)
expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest().encode()
if not any(hmac.compare_digest(v.encode(), expected) for v in candidates):
abort(400)
event = request.get_json(force=True)
# event["id"] 로 중복 처리를 막으세요.
return "", 200재시도 규칙
- 2xx 응답이 아니거나 10초 안에 응답이 없으면 실패로 봅니다. 수신 서버는 2xx 를 빨리 돌려주고, 오래 걸리는 일은 큐로 넘기세요.
- 최대 5회까지 시도합니다 (기본값이며 운영에서 조정할 수 있습니다).
- 모두 실패하면 소진(DEAD) 상태가 되고, 운영 담당이 수동으로 재발송할 수 있습니다.
- 같은 엔드포인트에서 연속 20회 실패하면 엔드포인트가 자동으로 일시 중지(PAUSED)되고 운영 담당에게 알림이 갑니다. 중지 중에 생긴 이벤트는 소진 상태로 보관되며, 재개한 뒤 재발송할 수 있습니다.
- 리다이렉트(3xx)는 따라가지 않습니다.
- 라이브 엔드포인트는 https 주소만 쓸 수 있고, 내부망 주소는 쓸 수 없습니다.
- 응답 본문은 저장하지 않습니다.
POST /webhooks/test로 샘플 이벤트(본문에data.sample: true)를 바로 받아볼 수 있습니다. 재시도나 상태 변경은 없습니다.
| 실패 횟수 | 다음 시도까지 |
|---|---|
| 1회 실패 | 10초 |
| 2회 실패 | 1분 |
| 3회 실패 | 5분 |
| 4회 실패 | 15분 |
| 5회 이상 실패 | 1시간 (최대 시도 횟수를 5회보다 크게 설정한 경우) |
최대 시도 횟수에 도달한 실패는 더 이상 자동 재시도하지 않고 소진(DEAD)으로 남습니다.
멱등(Idempotency)
같은 요청이 두 번 처리되는 일을 막기 위해 쓰기 요청(POST)에는 Idempotency-Key 헤더를 보냅니다. 네트워크 오류로 응답을 받지 못했을 때도 안전하게 다시 보낼 수 있습니다.
- 키는 1~255자의 공백 없는 영문·숫자·기호입니다. 요청마다 새 값(예: UUID)을 만들고, 재시도할 때는 같은 값을 다시 보내세요.
- 24시간 동안 같은 키와 같은 내용으로 요청하면 첫 성공(2xx) 응답을 그대로 재생하고, 응답 헤더
X-RoadMax-Idempotent-Replay: true가 붙습니다. - 같은 키에 다른 내용으로 요청하면 422
idempotency_key_reuse입니다. - 같은 키의 첫 요청이 아직 처리 중이면 409
idempotency_in_progress입니다. 잠시 뒤 같은 키로 다시 요청하세요. - 실패 응답은 저장되지 않으므로, 실패한 요청은 같은 키로 다시 시도할 수 있습니다.
POST /deliveries는 필수입니다. 없으면 400idempotency_key_required입니다.
접수 요청이 시간 초과나 네트워크 오류로 끝났다면 새 키를 만들지 말고 같은 키로 다시 요청하세요. 이미 접수됐다면 첫 응답이 재생되고, 아직 접수되지 않았다면 새로 접수됩니다. 이렇게 하면 같은 주문이 두 번 접수되지 않습니다.
오류 코드
오류는 HTTP 상태 코드와 함께 아래 형식의 JSON 으로 내려옵니다.
{
"error": {
"code": "invalid_api_key",
"message": "API 키가 올바르지 않습니다.",
"requestId": "req_3f9a1c7d2b8e4a6f10c5d9e2"
}
}code— 기계가 읽는 오류 코드. 분기 처리에는message대신code를 쓰세요.message문구는 바뀔 수 있습니다.details— 입력 검증 오류 등의 상세. 없을 수도 있습니다.requestId— 요청 식별자. 모든 응답의X-RoadMax-Request-Id헤더에도 같은 값이 있으니, 문의할 때 알려 주세요.
| HTTP | code | 설명 | 대응 |
|---|---|---|---|
| 400 | invalid_request | 요청 값 오류 또는 JSON 형식 오류. 값 오류는 details 에 항목별 사유가 있습니다. | 사유를 확인해 요청을 고친 뒤 다시 보내세요. |
| 400 | idempotency_key_required | POST /deliveries 에 Idempotency-Key 헤더가 없습니다. | 헤더를 추가하세요. |
| 400 | idempotency_key_invalid | Idempotency-Key 형식이 올바르지 않습니다 (1~255자, 공백 없음). | 형식에 맞는 키를 보내세요. |
| 401 | missing_api_key | X-RoadMax-Api-Key 헤더가 없습니다. | 헤더를 추가하세요. |
| 401 | invalid_api_key | API 키가 올바르지 않습니다. | 키 값과 환경(샌드박스·라이브)을 확인하세요. |
| 401 | api_key_revoked | 폐기된 키입니다. | 운영 담당에게 새 키를 요청하세요. |
| 401 | api_key_expired | 만료된 키입니다. | 운영 담당에게 새 키를 요청하세요. |
| 403 | partner_suspended | 정지된 파트너입니다. | 운영 담당에게 문의하세요. |
| 403 | insufficient_scope | 키에 필요한 스코프가 없습니다. | 운영 담당에게 스코프 추가를 요청하세요. |
| 403 | sandbox_only | 샌드박스 키 전용 엔드포인트를 라이브 키로 호출했습니다. | 샌드박스 키로 호출하세요. |
| 403 | feature_disabled | 사용 설정되지 않은 기능입니다 (예: 라이더 위치 공유 비활성). | 필요하면 운영 담당에게 활성화를 요청하세요. |
| 404 | not_found | 내 콜이 아니거나 없는 ID 입니다. | ID 와 키 환경(샌드박스·라이브)을 확인하세요. |
| 404 | store_not_mapped | 매핑되지 않았거나 중지된 매장 코드입니다 (견적 · 접수 공통). | GET /stores 로 매핑을 확인하고, 필요하면 운영 담당에게 문의하세요. |
| 409 | conflict | 요청이 리소스의 현재 상태와 맞지 않습니다 (같은 partnerRef 의 미취소 콜이 이미 있음, 픽업 이후 · 취소된 콜의 수정, 배차 뒤(assigned 부터) · 취소된 콜의 취소, 완료된 샌드박스 콜의 진행 등). | 콜 상태를 조회한 뒤 다시 판단하세요. 배차 뒤 취소는 담당 지점에 문의하세요. |
| 409 | branch_min_riders | 담당 지점의 배차 가능 라이더가 부족해 지금은 접수할 수 없습니다 (현재 배차 가능한 라이더가 부족합니다). | 잠시 뒤 같은 요청을 다시 보내세요. |
| 409 | idempotency_in_progress | 같은 Idempotency-Key 의 첫 요청이 아직 처리 중입니다. | 잠시 뒤 같은 키로 다시 요청하세요. |
| 413 | payload_too_large | 요청 본문이 너무 큽니다 (최대 100kb). | 본문을 줄여서 다시 보내세요. |
| 422 | idempotency_key_reuse | 같은 Idempotency-Key 를 다른 내용의 요청에 썼습니다. | 요청마다 새 키를 만드세요. |
| 422 | unprocessable | 업무 규칙 위반입니다 (담당 지점 접수 불가, 매장 운영 중지, 위치 확인 불가, 매장 캐시 부족 · 미정산 등). details (reason 또는 field)에 사유가 담깁니다. | 사유에 맞게 요청을 고치거나, 매장 캐시 문제는 매장에서 충전한 뒤 다시 시도하세요. 그 밖에는 운영 담당에게 문의하세요. |
| 429 | rate_limited | 분당 호출 한도를 넘었거나, 같은 곳에서 인증 실패가 너무 많이 쌓였습니다. | Retry-After 초만큼 기다린 뒤 다시 요청하세요 (8장). 인증 실패가 원인이면 키 값부터 확인하세요. |
| 500 | internal_error | 서버 내부 오류입니다. | 잠시 뒤 다시 시도하고, 계속되면 requestId 와 함께 문의하세요. |
| 503 | service_unavailable | 일시적으로 서비스를 쓸 수 없습니다. | 잠시 뒤 다시 시도하세요. |
속도 제한
API 키마다 분당 호출 한도가 있습니다. 기본은 분당 120회이고, 키마다 다르게 조정할 수 있습니다.
| 응답 헤더 | 의미 |
|---|---|
X-RateLimit-Limit | 이 키의 분당 호출 한도 |
X-RateLimit-Remaining | 현재 구간에서 남은 호출 수 |
X-RateLimit-Reset | 한도가 초기화되는 시각 (unix 초) |
- 한도를 넘으면 429
rate_limited와 함께Retry-After(초) 헤더가 내려옵니다. 그 시간만큼 기다린 뒤 다시 요청하세요. - 재시도는 지수 백오프(1초, 2초, 4초 … 처럼 간격을 늘리기)를 권장합니다. 한꺼번에 몰아서 다시 보내지 마세요.
- 웹훅 수신은 이 한도와 무관합니다.
변경 이력
| 버전 | 내용 |
|---|---|
2026-10-02 | v1 최초 공개 — 매장 · 견적 · 접수 · 조회 · 수정 · 취소 · 웹훅 · 샌드박스. 응답 헤더 X-RoadMax-Api-Version: 2026-10-02 |
필드 추가는 예고 없이 있을 수 있으니 모르는 필드는 무시하세요. 필드 삭제나 의미 변경은 새 버전 날짜로 사전에 공지합니다.