공급업체 API: 경쟁하지 않고 수동 주문 클레임하기
이 가이드의 공개 버전에서는 GET /orders를 폴링하라고 안내했습니다. 그 엔드포인트는 실행 중인 모든 폴러에게 동일한 주문을 전달합니다. 주문을 원자적으로 점유하는 엔드포인트, 직접 가져와야 하는 리스(lease), 그리고 아무도 문서화하지 않은 두 개의 속도 제한이 있습니다. 실제로 부하 상황에서도 유지되는 루프는 다음과 같습니다.
Sarah JohnsonAPI를 통해 수동 주문을 처리한다면, 첫 번째 결정은 어떤 엔드포인트가 작업을 가져올지입니다. 그리고 이 결정은 대부분의 통합에서 잘못 내려집니다. GET /orders는 읽기 전용입니다. 요청하는 사람에게 일치하는 주문을 반환할 뿐 아무것도 변경하지 않습니다. 두 개의 워커를 실행하거나 재시도가 있는 워커 하나를 실행하면 둘 다 같은 주문을 받게 되고 둘 다 배송을 시도하게 됩니다.
작업을 가져오는 엔드포인트는 POST /orders-pull입니다. 대기 중인 수동 주문을 선택하고, 각 주문을 compare-and-set 방식으로 대기에서 처리 중으로 전환한 다음, 실제로 전환된 주문만 반환합니다. 두 번째 워커가 1밀리초 후에 요청하면 compare가 실패하고 해당 주문은 응답에 포함되지 않습니다. 이 엔드포인트의 자체 소스 노트에는 PerfectPanel 호환으로 설명되어 있으며, 이는 대부분의 패널 통합이 이미 기대하는 형태입니다.
실행 순서에 따른 루프
- 작업 가져오기.
POST /orders-pull. 선택적 본문limit, 기본값 100, 최대 500. 가장 오래된 주문부터. - 리스(워커가 느린 경우).
PATCH /orders/{id}/last-process-time본문 없이 호출하면 현재 시간이 기록되어 30분 동안 자체 폴링에서 주문이 숨겨집니다. - 배송. 자격 증명 라인과 함께
POST /orders-update를 호출하거나, 대량의 경우POST /orders/{id}/accounts-bulk를 호출합니다. - 완료. 동일한
orders-update호출에status: "completed"를 포함하거나, 일부만 채운 경우"partial"을 포함합니다.
인증은 모든 호출에 헤더로 포함되며, 두 가지 허용 형식이 있습니다:
curl -X POST "https://api.hstockplus.com/api/admin/v2/orders-pull" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
# 동일한 방식
curl -X POST "https://api.hstockplus.com/api/admin/v2/orders-pull" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
키는 대시보드의 API 설정 페이지에서 생성합니다. 헤더가 없으면 API_KEY_REQUIRED가 반환되고, 잘못된 키는 INVALID_API_KEY가 반환됩니다. 이는 서로 다른 코드이며 서로 다른 버그를 의미합니다.
30분 창은 리스이며, 선택 사항입니다
두 목록 엔드포인트 모두 lastProcessTime이 지난 30분 이내인 주문을 건너뜁니다. 자동 중복 제거처럼 들리지만 그렇지 않습니다. 어느 엔드포인트도 이 필드를 쓰지 않기 때문입니다. PATCH /orders/{id}/last-process-time을 통한 사용자만 이 필드를 씁니다.
따라서 이 필드는 협력적 리스입니다. 주문을 가져올 때 이 필드를 기록하면 다음 폴링에서 30분 동안 주문이 사라집니다. 배송에 시간이 걸리고 폴러가 매분 실행된다면 원하는 동작입니다. 명시적 ISO 타임스탬프를 보내면 다른 만료 시간을 설정하고, null을 보내면 즉시 해제합니다. 엔드포인트를 완전히 무시하면 이 창은 적용되지 않습니다.
orders-pull은 상태 전환을 통해 이중 가져오기를 이미 방지합니다. 리스는 두 번째 실패 모드를 위한 것입니다: 워커가 주문을 가져갔고 아직 작업 중이며, 그 사이에 다시 보고 싶지 않은 경우입니다.
사람들을 놀라게 하는 필터 기본값으로 주문 읽기
GET /orders는 여전히 대사 조정, 백필 및 대시보드에 적합한 엔드포인트입니다. 그 위에 구축하기 전에 알아야 할 두 가지 기본값이 있습니다.
- 결제 상태 기본값은 하나가 아닌 두 값입니다.
paymentStatus매개변수 없이completed또는partial주문을 받습니다. Partial은 부분 환불을 의미하며, 이러한 주문은 여전히 진행 중인 작업입니다. 매개변수 자체는 pending, completed, failed, refunded, processing 및 partial의 쉼표로 구분된 목록을 허용합니다. - 제품 유형은 선택 사항이며 복수입니다. 수동 주문에는 필수가 아닙니다.
manual,inventory,auto를 쉼표로 구분하여 허용하므로productType=manual,auto가 유효합니다.
주문 상태 필터는 pending, processing, completed, refunded 및 error를 허용합니다. partial은 그 중에 없습니다. 이는 결제 상태입니다. 기타 필터: entityType은 product 또는 smm_service, subCategory는 이름으로, createdFrom 및 createdTo는 ISO 인스턴트, limit은 최대 500, offset입니다.
curl -X GET "https://api.hstockplus.com/api/admin/v2/orders\
?status=pending,processing&productType=manual&paymentStatus=completed,partial&limit=50" \
-H "X-Api-Key: YOUR_API_KEY"
보내는 자격 증명 라인에 발생하는 일
API에 대한 대부분의 지원 티켓이 여기서 발생합니다. 저장 전에 세 가지 변환이 실행되며 그 중 어느 것도 응답에 표시되지 않기 때문입니다.
- 모든 요소는 줄바꿈으로 분할됩니다. 하나의 배열 항목이 전체 파일을 담을 수 있습니다.
["a:1\nb:2\nc:3"]와["a:1","b:2","c:3"]는 동일한 제출입니다. 빈 줄과 주변 공백은 제거됩니다. - 반복은 대소문자 구분 없이 삭제됩니다. 배치 내에서, 그리고 해당 주문에 이미 배송된 모든 항목에 대해. 타임아웃 후 동일한 배치를 다시 보내도 안전하며 두 번 배송되지 않습니다.
- 주문 수량을 초과하는 추가 라인은 조용히 폐기됩니다. 제출은 남은 슬롯으로 잘립니다. 오류가 발생하지 않으므로 올바른 수를 보내야 합니다.
라인에 포함된 내용은 전적으로 사용자에게 달려 있습니다. 플랫폼은 이를 하나의 불투명 문자열로 저장하고 절대 구문 분석하지 않으므로 콜론으로 구분된 쌍, 2FA 비밀번호가 있는 파이프로 구분된 삼중항, 토큰, 세션 블롭: 모두 그냥 텍스트입니다. 목록 설명이 약속하는 규칙이 구매자가 기대하는 규칙이며, 이를 강제하는 것은 없습니다.
이전 배송에 대한 중복이 거부되지 않고 삭제되므로 여러 호출에 걸친 부분 배송이 지원되는 패턴입니다. 가진 것을 보내고 주문을 partial로 표시한 다음 나중에 나머지를 보내고 completed로 표시합니다.
지금까지 문서화되지 않은 두 가지 속도 제한
orders-update는 API 키당 최소 간격(기본 200밀리초)과 사용자당 동시 요청 2개의 동시성 상한을 갖습니다. 둘 중 하나를 초과하면 HTTP 429와 함께 기계가 읽을 수 있는 지연이 반환됩니다:
{ "error": "RATE_LIMIT", "message": "Too many order update requests",
"retry_after_ms": 137 }
{ "error": "CONCURRENCY_LIMIT", "message": "Too many concurrent sync requests",
"retry_after_ms": 5000 }
retry_after_ms를 읽고 그 시간만큼 대기합니다. 고정 백오프는 200ms 경우에는 너무 느리거나 동시성 경우에는 너무 빠릅니다. 또한 주문별 뮤텍스가 있습니다: 같은 주문에 두 배송이 경쟁하면 ORDER_DELIVERY_BUSY가 발생하며, 이것도 429이고 올바른 응답은 다른 자격 증명을 다시 보내는 것이 아니라 짧은 재시도입니다.
별도로 처리할 가치가 있는 오류 코드
API_KEY_REQUIRED및INVALID_API_KEY: 헤더 없음 대 캐시가 인식하지 못하는 헤더.ORDER_NOT_FOUND: 주문 ID가 캐시에 없습니다. 일반 공급업체도 새로 생성된 자동 주문에 대해 짧은 가시성 지연으로 아무것도 표시되지 않을 수 있으므로 생성 후 몇 초 후의 404가 반드시 영구적인 것은 아닙니다.ACCESS_DENIED: 주문에 사용자에게 속한 라인이 없습니다.PAYMENT_NOT_COMPLETED: 미결제 또는 전액 환불. 배송할 수 없으며 재시도해서는 안 됩니다.INVALID_ACCOUNTS, 메시지 "No valid accounts provided": 배열이 아무것도 없는 것으로 평탄화되었습니다. 일반적으로 빈 문자열 또는 문자열이 아닌 배열입니다.RATE_LIMIT,CONCURRENCY_LIMIT,ORDER_DELIVERY_BUSY: 백오프 후 동일한 요청을 재시도합니다.
자체 기록을 위한 두 필드
orders-update는 내부 주문 참조를 위한 external_id를 허용하며, 패널이 이미 보내는 형태라면 별칭으로 supplierOrderId도 허용합니다. 또한 업스트림에서 주문 비용을 기록하는 숫자 external_price도 허용합니다. 이 수치는 참조용으로 저장되며 플랫폼 합계에는 영향을 미치지 않습니다. null 또는 빈 문자열을 보내면 이미 있는 값을 그대로 두므로 반복 업데이트가 이를 지우지 않습니다.
manual이 실제로 의미하는 것과 실제 속도
제품 유형 manual은 공급업체가 주문 도착 후 상품을 업로드한다는 의미입니다. 배송 속도 설정이 아니며 기록에 명확히 명시되어 있습니다. 사이트의 모든 완료된 수동 제품 주문(6,552건)에서 결제부터 완료까지의 중앙값은 30초 미만이며, 4분의 3은 10분 이내에 완료되고 약 40건 중 1건만 하루 이상 걸립니다.
인벤토리 주문은 행이 이미 있으므로 사실상 즉시입니다. 자동 주문은 그 사이에 있습니다. manual이 거의 빠르게 보이는 이유는 볼륨을 처리하는 공급업체가 대시보드를 보는 대신 짧은 폴링으로 이 API를 실행하기 때문입니다. 통합이 몇 분을 추가한다면 사용자가 느린 꼬리이지 표준이 아닙니다.
계정이 아닌 성장 서비스를 판매하는 경우 알아야 할 시계 하나: 성장 서비스 주문은 배송된 순간부터 목록에 무엇이 적혀 있든 정확히 3일의 애프터세일 기간을 갖습니다. 계정 목록은 대신 목록별로 설정된 자체 보증을 갖습니다.
제대로 작동하는 워커
const BASE = 'https://api.hstockplus.com/api/admin/v2';
const HEAD = { 'X-Api-Key': process.env.API_KEY, 'Content-Type': 'application/json' };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function call(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: HEAD,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json().catch(() => ({}));
if (res.status === 429) {
await sleep(json.retry_after_ms ?? 1000);
return call(method, path, body);
}
if (!res.ok) throw Object.assign(new Error(json.message || res.status), { code: json.error });
return json;
}
async function tick() {
const { orders = [] } = await call('POST', '/orders-pull', { limit: 50 });
for (const order of orders) {
// 주문은 이미 'processing' 상태이며 사용자 것입니다. 느리다면 리스를 가져오세요.
await call('PATCH', '/orders/' + order.id + '/last-process-time', {});
const accounts = await prepareCredentials(order); // 사용자 시스템
await call('POST', '/orders-update', {
order: order.id,
status: accounts.length >= order.quantity ? 'completed' : 'partial',
accounts,
supplierOrderId: 'SUP-' + order.id,
});
}
}
이 루프가 이전 형태와 다른 세 가지: 읽는 대신 가져오고, 추측 대신 서버의 자체 재시도 지연을 존중하며, 채울 수 없는 주문을 닫는 대신 partial을 정직하게 보고합니다.
제품 생성, 티켓, 환불 및 대량 계정 작업을 포함한 전체 엔드포인트 참조는 공급업체 API 페이지에 있습니다. 목록을 manual, inventory 또는 auto 중 어떤 것으로 할지 아직 결정 중이라면 그 선택은 제품 관리 가이드에서 다룹니다.



