PARTNER API · 2026-10-02

ROADMAX 개발자 센터

파트너 연동 API 로 배달 접수와 조회를 연동하고, 웹훅으로 진행 상황을 실시간으로 받아 보세요. 샌드박스에서 먼저 시험한 뒤 라이브 키로 전환할 수 있습니다.

REST · JSON웹훅 서명 검증멱등 접수샌드박스 제공
기본 URLhttps://roadmax.kr/api/v1/partner/v1
목차

개요

ROADMAX 파트너 연동 API 는 POS · 배달대행 · 앱 같은 외부 시스템이 ROADMAX 배달망에 배달을 접수하고, 접수한 콜을 조회하며, 상태가 바뀔 때마다 웹훅으로 알림을 받도록 하는 REST API 입니다.

  • 견적 — 배달지까지의 안내 요금을 접수 전에 확인합니다.
  • 접수 · 수정 · 취소 — 파트너 주문 번호와 함께 배달을 접수하고, 허용되는 상태에서 수정하거나 취소합니다.
  • 조회 — 내가 접수한 콜의 상태와 배정된 라이더(가려진 정보)를 조회합니다.
  • 웹훅 — 접수 · 배정 · 픽업 · 완료 · 취소 · 수정 이벤트를 내 서버로 받습니다.
  • 샌드박스 — 실제 배달과 분리된 환경에서 전체 흐름을 먼저 시험합니다.
항목내용
기본 URLhttps://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"

샌드박스 시작하기

아래 순서대로 따라 하면 접수부터 완료까지 한 번에 확인할 수 있습니다. 예제의 키와 시크릿은 모두 가짜 값이니 발급받은 값으로 바꿔서 실행하세요.

  1. 운영 담당에게 요청합니다

    다음 네 가지를 한 번에 요청하면 빠릅니다.

    • 파트너 등록
    • 샌드박스 API 키 발급
    • 시험 매장 매핑 (파트너 매장 코드와 시험 상점 연결)
    • 웹훅 수신 URL 등록 (서명 시크릿 whsec_… 발급)

    키와 시크릿은 발급 화면에서 한 번만 보입니다. 받는 즉시 안전한 곳에 보관하세요.

  2. GET /stores 로 매핑을 확인합니다

    운영 담당이 등록한 파트너 매장 코드가 보이면 인증과 매핑이 정상입니다.

    bash
    curl -s "https://roadmax.kr/api/v1/partner/v1/stores" \
      -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  3. POST /quotes 로 견적을 받습니다

    배달지 좌표(또는 주소)를 보내면 안내 요금이 내려옵니다. 응답 예제는 4장 견적을 참고하세요.

    bash
    curl -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"
      }
    }'
  4. POST /webhooks/test 로 서명 검증 코드를 확인합니다

    등록한 웹훅 URL 로 샘플 이벤트(본문에 data.sample: true)가 즉시 전송됩니다. 5장 서명 검증의 예제 코드로 검증이 통과하는지 확인하세요.

    bash
    curl -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"
    }'
  5. POST /deliveries 로 접수합니다

    Idempotency-Key 헤더는 필수입니다. 응답 예제는 4장 접수를 참고하세요.

    bash
    curl -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": "문 앞에 놓아주세요"
    }'
  6. POST /sandbox/deliveries/:id/advance 로 콜을 진행시킵니다

    배정 → 픽업 → 완료 순서로 진행시키며 각 단계의 웹훅(delivery.assigned, delivery.picked_up, delivery.delivered)이 도착하는지 확인합니다. 샌드박스 키 전용입니다.

    bash
    curl -s -X POST "https://roadmax.kr/api/v1/partner/v1/sandbox/deliveries/cm1x2y3z40000abcd1234efgh/advance" \
      -H "X-RoadMax-Api-Key: rmk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  7. 라이브 키로 바꾸기 전 체크리스트
    • 웹훅 서명 검증을 구현했고, 잘못된 서명과 오래된 타임스탬프를 거절하는지 확인했다.
    • 같은 이벤트가 다시 와도 한 번만 처리한다 (이벤트 id 로 중복 제거).
    • 접수 요청마다 Idempotency-Key 를 보내고, 응답을 받지 못했을 때는 같은 키로 다시 요청한다.
    • 웹훅 수신은 2xx 를 빨리 돌려주고, 재시도 · 소진(DEAD) · 일시 중지(PAUSED) 규칙을 이해했다.
    • 429 rate_limited 를 받으면 Retry-After 만큼 기다렸다가 재시도한다.

    위 항목을 확인한 뒤 운영 담당에게 라이브 키 발급을 요청하세요. 라이브 키는 실제 배달 접수에 쓰이므로 샌드박스에서 먼저 충분히 시험해야 합니다.

접수 흐름

콜(배달 한 건)은 보통 다음 순서로 진행됩니다.

  1. 견적 (선택)

    POST /quotes 로 배달지까지의 안내 요금을 확인합니다.

  2. 접수

    POST /deliveries 로 배달을 접수합니다. Idempotency-Key 헤더가 필수이며, 요금을 생략하면 견적 금액으로 접수됩니다.

  3. 상태 확인

    상태가 바뀔 때마다 웹훅으로 알림을 받는 방식을 권장합니다. 필요하면 GET /deliveries/:id 로 직접 조회할 수도 있습니다.

  4. 수정 · 취소 (필요할 때)

    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 헤더 필수, 성공하면 201deliveries:write
GET/deliveries/:id콜 단건 조회deliveries:read
GET/deliveries콜 목록 — from, to, status, partnerRef, cursor, limitdeliveries:read
PATCH/deliveries/:id배달지 주소(상세 · 좌표) · 배달비 · 메모 수정 — 픽업 전(pending · assigned)의 콜만deliveries:write
POST/deliveries/:id/cancel콜 취소 — 배차 전(pending)의 콜만(배차 뒤는 409, 지점에 문의), 본문 { "reason": "..." } 은 선택deliveries:write
GET/deliveries/:id/rider-location라이더 위치(옵션) — 기본 비활성, 꺼져 있으면 403 feature_disabledrider_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
응답 200
{
  "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"
  }
}
응답 200
{
  "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자 이내). 응답·웹훅의 콜 객체에 같은 값으로 내려옵니다.
응답 201 — 접수 직후(배정 전)
{
  "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 로 다시 요청하면 첫 응답이 그대로 재생됩니다. 네트워크 오류로 응답을 받지 못했다면 같은 키로 다시 요청하세요.

접수가 거절되는 경우

HTTPcode사유
400invalid_request값 형식 오류, 배달지 주소 · 좌표가 모두 없음, 배달비를 계산할 좌표가 없음 등 (details 확인)
404store_not_mapped매핑되지 않았거나 중지된 매장 코드
409conflict같은 partnerRef 의 취소되지 않은 콜이 이미 있음 (details.deliveryId 가 기존 콜)
409branch_min_riders담당 지점의 배차 가능 라이더가 부족해 지금은 접수할 수 없음. 잠시 뒤 다시 시도하세요.
422unprocessabledetails.reason 로 구분: branch_unavailable(담당 지점이 접수를 받지 않음) · store_inactive(매장 운영 중지) · store_cash_insufficient · store_minus_outstanding · store_minus_cap_exceeded(매장의 캐시 잔액 부족 · 미정산) · delivery_fee_below_min(배달비가 하한 미만, details.min 이 하한) — 좌표 오류는 details.field
오류 예시 — 409 branch_min_riders
{
  "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)
요청 (시각의 + 기호는 %2B 로 인코딩)
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"
응답 200
{
  "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": "벨을 눌러 주세요"
}
응답 200
{
  "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": "고객 요청으로 취소"
}
응답 200
{
  "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
응답 200 — 위치가 있을 때
{
  "data": {
    "deliveryId": "cm1x2y3z40000abcd1234efgh",
    "status": "assigned",
    "location": {
      "lat": 36.9291,
      "lng": 127.6872,
      "updatedAt": "2026-10-02T01:26:40.000Z"
    }
  }
}
응답 200 — 위치가 없을 때
{
  "data": {
    "deliveryId": "cm1x2y3z40000abcd1234efgh",
    "status": "pending",
    "location": null
  }
}
  • location 은 콜이 배정된 뒤 완료되기 전(assigned · picked_up · in_transit)이고 최근 5분 안에 갱신된 위치일 때만 값이 있습니다. 배정 전 · 완료 · 취소된 콜과 오래된 위치는 null 입니다.
  • updatedAt 은 라이더 위치가 마지막으로 갱신된 시각입니다. 라이더 이름 · 연락처 같은 식별 정보는 이 응답에 없습니다.
  • 내 콜이 아니거나 없는 ID 는 404 not_found 입니다.
기본 상태의 응답 403
{
  "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"
}
응답 200
{
  "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
응답 200
{
  "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 을 넣은 엔드포인트에만 나갑니다.

모든 이벤트는 같은 모양의 본문(봉투)으로 옵니다.

delivery.assigned 이벤트 본문 예시
{
  "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이벤트 종류 (위 표)
apiVersionAPI 버전 날짜
createdAt이벤트가 발생한 시각 (ISO 8601, UTC)
livemode라이브 콜이면 true, 샌드박스 콜이면 false
data.delivery이벤트를 만든 시점의 최신 콜 객체. 짧은 시간에 여러 단계가 지나가면 이벤트 종류와 status 가 다를 수 있습니다(예: delivery.created 인데 status 가 assigned). 재시도할 때도 같은 본문을 다시 보냅니다.
data.editdelivery.edited 에만 포함 — field(수정된 항목), pending(수정이 대기 중인 요청인지 여부), actorRole(수정한 쪽의 역할)
data.locationrider.location 에만 포함 — lat, lng, at 만 담기며 라이더 식별 정보는 없습니다
data.samplePOST /webhooks/test 의 샘플 이벤트에만 true
delivery.edited — data.edit 예시
{
  "edit": {
    "field": "memo",
    "pending": false,
    "actorRole": "OWNER"
  }
}
rider.location — data.location 예시 (data.delivery 와 함께 옵니다)
{
  "location": {
    "lat": 36.9291,
    "lng": 127.6872,
    "at": "2026-10-02T01:26:40.000Z"
  }
}
순서와 중복에 대비하세요
  • 순서는 보장되지 않습니다. 이벤트가 늦게 도착할 수 있으니 도착 순서가 아니라 createdAt 과 data.delivery.status 로 최신 상태를 판단하세요. 이벤트 종류는 "무슨 일이 있었는지"를, status 는 "지금 상태"를 알려 줍니다.
  • 같은 이벤트가 중복으로 올 수 있습니다. 이벤트 id 를 기록해 두었다가 이미 처리한 id 는 건너뛰세요.

요청 헤더

헤더값
Content-Typeapplication/json; charset=utf-8
User-AgentRoadMax-Webhook/1.0
X-RoadMax-Signature서명 — t=<unix초>,v1=<hex> (아래 서명 검증)
X-RoadMax-Event-Id이벤트 ID
X-RoadMax-Event-Type이벤트 종류
X-RoadMax-Api-VersionAPI 버전 날짜
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)
// 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)
# 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 는 필수입니다. 없으면 400 idempotency_key_required 입니다.
응답을 받지 못했다면

접수 요청이 시간 초과나 네트워크 오류로 끝났다면 새 키를 만들지 말고 같은 키로 다시 요청하세요. 이미 접수됐다면 첫 응답이 재생되고, 아직 접수되지 않았다면 새로 접수됩니다. 이렇게 하면 같은 주문이 두 번 접수되지 않습니다.

오류 코드

오류는 HTTP 상태 코드와 함께 아래 형식의 JSON 으로 내려옵니다.

오류 응답 예시
{
  "error": {
    "code": "invalid_api_key",
    "message": "API 키가 올바르지 않습니다.",
    "requestId": "req_3f9a1c7d2b8e4a6f10c5d9e2"
  }
}
  • code — 기계가 읽는 오류 코드. 분기 처리에는 message 대신 code 를 쓰세요. message 문구는 바뀔 수 있습니다.
  • details — 입력 검증 오류 등의 상세. 없을 수도 있습니다.
  • requestId — 요청 식별자. 모든 응답의 X-RoadMax-Request-Id 헤더에도 같은 값이 있으니, 문의할 때 알려 주세요.
HTTPcode설명대응
400invalid_request요청 값 오류 또는 JSON 형식 오류. 값 오류는 details 에 항목별 사유가 있습니다.사유를 확인해 요청을 고친 뒤 다시 보내세요.
400idempotency_key_requiredPOST /deliveries 에 Idempotency-Key 헤더가 없습니다.헤더를 추가하세요.
400idempotency_key_invalidIdempotency-Key 형식이 올바르지 않습니다 (1~255자, 공백 없음).형식에 맞는 키를 보내세요.
401missing_api_keyX-RoadMax-Api-Key 헤더가 없습니다.헤더를 추가하세요.
401invalid_api_keyAPI 키가 올바르지 않습니다.키 값과 환경(샌드박스·라이브)을 확인하세요.
401api_key_revoked폐기된 키입니다.운영 담당에게 새 키를 요청하세요.
401api_key_expired만료된 키입니다.운영 담당에게 새 키를 요청하세요.
403partner_suspended정지된 파트너입니다.운영 담당에게 문의하세요.
403insufficient_scope키에 필요한 스코프가 없습니다.운영 담당에게 스코프 추가를 요청하세요.
403sandbox_only샌드박스 키 전용 엔드포인트를 라이브 키로 호출했습니다.샌드박스 키로 호출하세요.
403feature_disabled사용 설정되지 않은 기능입니다 (예: 라이더 위치 공유 비활성).필요하면 운영 담당에게 활성화를 요청하세요.
404not_found내 콜이 아니거나 없는 ID 입니다.ID 와 키 환경(샌드박스·라이브)을 확인하세요.
404store_not_mapped매핑되지 않았거나 중지된 매장 코드입니다 (견적 · 접수 공통).GET /stores 로 매핑을 확인하고, 필요하면 운영 담당에게 문의하세요.
409conflict요청이 리소스의 현재 상태와 맞지 않습니다 (같은 partnerRef 의 미취소 콜이 이미 있음, 픽업 이후 · 취소된 콜의 수정, 배차 뒤(assigned 부터) · 취소된 콜의 취소, 완료된 샌드박스 콜의 진행 등).콜 상태를 조회한 뒤 다시 판단하세요. 배차 뒤 취소는 담당 지점에 문의하세요.
409branch_min_riders담당 지점의 배차 가능 라이더가 부족해 지금은 접수할 수 없습니다 (현재 배차 가능한 라이더가 부족합니다).잠시 뒤 같은 요청을 다시 보내세요.
409idempotency_in_progress같은 Idempotency-Key 의 첫 요청이 아직 처리 중입니다.잠시 뒤 같은 키로 다시 요청하세요.
413payload_too_large요청 본문이 너무 큽니다 (최대 100kb).본문을 줄여서 다시 보내세요.
422idempotency_key_reuse같은 Idempotency-Key 를 다른 내용의 요청에 썼습니다.요청마다 새 키를 만드세요.
422unprocessable업무 규칙 위반입니다 (담당 지점 접수 불가, 매장 운영 중지, 위치 확인 불가, 매장 캐시 부족 · 미정산 등). details (reason 또는 field)에 사유가 담깁니다.사유에 맞게 요청을 고치거나, 매장 캐시 문제는 매장에서 충전한 뒤 다시 시도하세요. 그 밖에는 운영 담당에게 문의하세요.
429rate_limited분당 호출 한도를 넘었거나, 같은 곳에서 인증 실패가 너무 많이 쌓였습니다.Retry-After 초만큼 기다린 뒤 다시 요청하세요 (8장). 인증 실패가 원인이면 키 값부터 확인하세요.
500internal_error서버 내부 오류입니다.잠시 뒤 다시 시도하고, 계속되면 requestId 와 함께 문의하세요.
503service_unavailable일시적으로 서비스를 쓸 수 없습니다.잠시 뒤 다시 시도하세요.

속도 제한

API 키마다 분당 호출 한도가 있습니다. 기본은 분당 120회이고, 키마다 다르게 조정할 수 있습니다.

응답 헤더의미
X-RateLimit-Limit이 키의 분당 호출 한도
X-RateLimit-Remaining현재 구간에서 남은 호출 수
X-RateLimit-Reset한도가 초기화되는 시각 (unix 초)
  • 한도를 넘으면 429 rate_limited 와 함께 Retry-After(초) 헤더가 내려옵니다. 그 시간만큼 기다린 뒤 다시 요청하세요.
  • 재시도는 지수 백오프(1초, 2초, 4초 … 처럼 간격을 늘리기)를 권장합니다. 한꺼번에 몰아서 다시 보내지 마세요.
  • 웹훅 수신은 이 한도와 무관합니다.

변경 이력

버전내용
2026-10-02v1 최초 공개 — 매장 · 견적 · 접수 · 조회 · 수정 · 취소 · 웹훅 · 샌드박스. 응답 헤더 X-RoadMax-Api-Version: 2026-10-02
호환성 정책

필드 추가는 예고 없이 있을 수 있으니 모르는 필드는 무시하세요. 필드 삭제나 의미 변경은 새 버전 날짜로 사전에 공지합니다.

ROADMAX 개발자 센터 · 파트너 연동 API v1

문의: 운영 담당(RoadMax 파트너 연동)

상호㈜다운타운컴퍼니대표김경자주소충청북도 음성군 맹동면 대하로 208, 도시빌딩 202호사업자등록번호361-81-03385통신판매업 신고번호제2026-충북음성-0336호전화043-882-1582이메일dtcbiz@naver.com