NinjaBuy Business API
기본 URL
이 사이트의 모든 경로는 하나의 기본 URL 아래에 있어요. Shopify 앱도, 내가 만드는 것도 같은 경로를 호출하므로 앱이 하는 일은 내 코드도 할 수 있어요.
https://www.ninjabuy.com/api/v1
AI 어시스턴트에게 묻고 싶나요? 같은 계정이 MCP https://www.ninjabuy.com/mcp에서도 작동해요: AI 어시스턴트(MCP).
curl "https://www.ninjabuy.com/api/v1/balance" \
-H "Authorization: Bearer $NINJABUY_API_KEY" \
-H "NinjaBuy-Version: 2026-11"인증
모든 요청에 API 키를 bearer 토큰으로 보내세요. 키는 nb_로 시작해요. 프리뷰 기간에는 저희 팀이 발급해요. Business 계정 설정이 끝나면 요청한 범위로, 원하는 채널을 통해 각 키를 한 번만 보내 드려요. 폐기를 요청하면 다음 요청부터 작동하지 않아요.
키는 서버에 보관하세요. 브라우저, 앱, 공개 저장소에 있는 키는 누구나 읽을 수 있고 내 잔액을 쓸 수 있어요.
다른 NinjaBuy 셀러를 대신해 작동하는 앱은 OAuth 2.1을 사용해요: 동적 클라이언트 등록, PKCE(S256)를 사용한 인가 코드 흐름, 셀러 본인의 승인 화면. 액세스 토큰은 같은 헤더에 넣고 같은 경로에서 사용해요. 액세스 토큰은 1시간 유효하고, 리프레시 토큰으로 7일간 갱신해요. AI 어시스턴트도 같은 방식으로 연결돼요(MCP).
products:read범위- 가져오기, 상품, 옵션, 동기화 규칙 읽기.
products:write범위- 상품 가져오기, 수정, 보내기, 삭제. 동기화 규칙 변경.
inventory:read범위- 재고 수준 읽기.
orders:read범위- 주문, 풀필먼트 주문, 배송 읽기.
orders:write범위- 주문 생성, 보류, 해제, 취소. 잔액을 사용해요.
balance:read범위- 잔액과 거래 내역 읽기.
webhooks:write범위- 웹훅 엔드포인트를 관리해요.
키의 범위를 벗어난 호출은 403 insufficient-scope를 반환하고 필요한 범위를 알려 줘요.
Authorization: Bearer nb_7Qk2...vX9eHTTP/2 401
{
"type": "https://www.ninjabuy.com/api/v1/problems/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "The API key is missing, wrong, expired or revoked.",
"request_id": "req_7Hq2Lw9c"
}버전 관리
경로에는 메이저 버전 /v1이 들어가요. 그 안에서는 날짜가 붙은 버전이 필드와 동작을 바꿔요. 개발에 쓴 버전은 NinjaBuy-Version 헤더로 고정하세요. 모든 응답은 같은 헤더에 사용한 버전을 표시해요.
2026-11이 현재 버전이며 프리뷰예요. 안정 버전으로 표시되기 전까지 이름과 필드가 바뀔 수 있어요. 모든 변경 사항은 변경 내역에 있으며, 호환성을 깨는 변경 전에는 모든 키 보유자에게 이메일로 알려 드려요.
한 버전 안에서는 추가만 해요: 새 경로, 새 선택 매개변수, 응답의 새 필드, 새 이벤트 유형. 모르는 필드는 무시하도록 클라이언트를 만드세요.
GET /api/v1/orders
NinjaBuy-Version: 2026-11
HTTP/2 200
NinjaBuy-Version: 2026-11요청과 응답
- JSON은
Content-Type: application/json으로 보내세요. 응답은 UTF-8 JSON이에요. 오류는application/problem+json이에요. - 금액은 10진수 문자열과 통화로 된 객체예요:
{ "amount": "264.20", "currency": "CNY" }. NinjaBuy의 청구는 CN¥이고, 내 판매 가격은 스토어 통화예요. - ID는 종류를 나타내는 접두사가 붙은 문자열이에요(
prd_,ord_,shp_). 내부 구조에 의존하지 마세요. - 시각은 UTC 기준 ISO 8601이에요. 국가는 두 글자 ISO 코드예요.
- 모든 응답에는
X-Request-Id헤더가 있고, 모든 오류에는 같은 ID가request_id로 들어 있어요. 문의할 때 알려 주세요.
{
"id": "ord_3n7q",
"created_at": "2026-11-01T09:14:02Z",
"total": { "amount": "264.20", "currency": "CNY" },
"suggested_price": { "amount": "49.99", "currency": "USD" }
}페이지네이션
목록은 최신순으로 data, has_more, cursor로 반환돼요. has_more가 true인 동안 그 커서를 다시 보내 다음 페이지를 받으세요. 커서는 받을 때와 같은 필터에서만 작동해요.
limitinteger- 1~100. 생략하면 25.
cursorstring- 이전 페이지의 커서. 첫 페이지에서는 생략하세요.
curl "https://www.ninjabuy.com/api/v1/orders?limit=25&cursor=c_2v9kq" \
-H "Authorization: Bearer $NINJABUY_API_KEY" \
-H "NinjaBuy-Version: 2026-11"HTTP/2 200 OK
{
"data": [
{ "id": "ord_3n7q", "status": "checked" },
{ "id": "ord_3n6z", "status": "shipped" }
],
"has_more": true,
"cursor": "c_1q4ea"
}요청 한도
한도는 계정 단위이며 모든 키, OAuth 토큰, MCP 연결이 공유해요. 모든 응답은 IETF RateLimit-Policy와 RateLimit 헤더로 현재 상태를 알려 줘요. 거절은 429 rate-limited이며 Retry-After에 초 단위로 표시돼요.
| 시간 범위 | 해당하는 것 |
|---|---|
| "create-links";q=30;w=60 | 마켓플레이스에서 상품을 읽는 호출: 가져오기 생성·새로 고침, 상품 보내기. 분당 30회이며 링크 API, MCP와 공유해요. |
| "default";q=120;w=60 | 그 밖의 모든 호출. 분당 120회. |
HTTP/2 429
Content-Type: application/problem+json
Retry-After: 12
RateLimit-Policy: "create-links";q=30;w=60
RateLimit: "create-links";r=0;t=12
{
"type": "https://www.ninjabuy.com/api/v1/problems/rate-limited",
"title": "Too many requests",
"status": 429,
"detail": "Try again in 12 seconds.",
"request_id": "req_7Hq2Lw9c"
}멱등성
모든 POST에 Idempotency-Key(출력 가능한 ASCII 1~255자, UUID가 가장 좋아요)를 보내면 타임아웃 후 재시도로 두 번째 주문이 생기지 않아요. 24시간 동안 같은 경로, 같은 본문의 같은 키에는 첫 응답을 바이트 단위로 똑같이 Idempotent-Replayed: true와 함께 반환하며, 다시 실행하지도 한도에 포함하지도 않아요.
- 같은 키에 다른 본문이나 경로:
422 idempotency-key-reused. - 첫 요청이 아직 실행 중:
409 idempotency-key-in-progress,Retry-After포함. - 요청 한도 초과 거절은 응답이 아니에요. 키는 사용되지 않은 상태로 남으므로
Retry-After이후 다시 시도하면 요청이 실행돼요.
주문에는 내 external_id도 붙어요. 같은 ID의 두 번째 주문은 어떤 키로 보내든 409 duplicate-order로 거절돼요.
POST /api/v1/orders
Idempotency-Key: 4f0c2b4e-9d1a-4e0f-b0a8-5d1d3c7e2f61
HTTP/2 201
Idempotent-Replayed: true
X-Request-Id: req_7Hq2Lw9c오류
오류는 problem JSON(RFC 9457)이에요: type은 분기에 쓸 수 있는 고정 URL, title은 이름, detail은 이 경우를 보여 줄 수 있는 말로 설명하며, request_id로 저희 로그에서 찾을 수 있어요. 유효성 검사에 실패한 본문에는 errors가 추가되고, 각각 필드를 가리키는 JSON pointer가 있어요.
| 상태 | 유형 | 시점 |
|---|---|---|
| 400 | bad-json | 본문이 JSON이 아니에요. |
| 400 | bad-body | 필드가 없거나 잘못되었어요. errors[]가 해당 위치를 가리켜요. |
| 400 | bad-idempotency-key | Idempotency-Key가 비어 있거나, 너무 길거나, 출력 가능한 ASCII가 아니에요. |
| 401 | unauthorized | 키나 토큰이 없거나, 잘못되었거나, 만료되었거나, 폐기되었어요. |
| 403 | insufficient-scope | 키에 경로가 요구하는 범위가 없어요. |
| 404 | not-found | 이 계정에 해당 객체가 없어요. 다른 계정의 ID도 같은 응답이에요. |
| 405 | method-not-allowed | 경로는 있지만 이 메서드로는 안 돼요. Allow에 받는 메서드가 나와 있어요. |
| 409 | duplicate-order | 이 external_id의 주문이 있어요. order가 해당 주문을 가리켜요. |
| 409 | idempotency-key-in-progress | 이 키의 첫 요청이 아직 실행 중이에요. |
| 409 | invalid-state | 객체가 지금은 그 작업을 할 수 없어요. 예: 배송된 주문 취소. |
| 415 | unsupported-media-type | 본문이 application/json이 아니에요. |
| 422 | idempotency-key-reused | 키가 다른 요청에 사용되었어요. |
| 422 | unprocessable | 유효한 JSON이지만 처리할 수 없어요: 읽지 못한 링크, 품절된 옵션 등. detail에 무엇인지 나와요. |
| 429 | rate-limited | 시간 범위 안에 호출이 너무 많아요. Retry-After 초만큼 기다리세요. |
| 500 | internal-error | 저희 쪽 문제예요. 같은 Idempotency-Key로 다시 시도하세요. |
| 503 | service-unavailable | 지금은 키를 확인하거나 스토어에 연결할 수 없어요. Retry-After 이후 다시 시도하세요. |
HTTP/2 400
{
"type": "https://www.ninjabuy.com/api/v1/problems/bad-body",
"title": "Invalid request body",
"status": 400,
"detail": "1 field is invalid.",
"request_id": "req_7Hq2Lw9c",
"errors": [
{
"pointer": "/ship_to/country",
"detail": "Must be a two-letter country code."
}
]
}