Apinoa Docs

Response Schema

SearchResult, Product, ReviewPage, CategoryList, ShippingQuote — 모든 마켓플레이스가 돌려주는 정규화된 타입을 필드 단위로.

원본 .mdx 보기

다섯 가지 타입이 모든 마켓플레이스 오퍼레이션을 덮습니다. 한 필드는 어디에 나타나든 같은 것을 뜻하며, 모든 개념에는 이름이 정확히 하나입니다 — 배송은 검색 카드, 상품, 오퍼, 배송 견적 어디서나 같은 형태입니다.

선택 필드는 말 그대로 선택입니다. 없다는 것은 마켓플레이스가 말하지 않았다는 뜻이지, 기본값이 아닙니다. price가 없는 것은 무료 상품이 아니라 현재 판매 오퍼가 없는 리스팅입니다. delivery.cost가 없는 것은 무료배송이 아니라 배송비가 아직 산정되지 않았다는 뜻입니다 — 무료배송은 delivery.free: true, 전혀 다른 주장입니다.

어떤 선택 필드가 실제로 채워지는지는 마켓플레이스마다 크게 다릅니다. 각 마켓플레이스 문서에 측정된 비율이 있습니다.

SearchResult

{
  "marketplace": "amazon",
  "query": "running shoes",
  "page": { "index": 1, "size": 48, "total": 1200 },
  "items": [ /* SearchItem */ ],
  "cursor": "…"
}
필드타입비고
marketplacestring어느 마켓플레이스가 답했는지
querystring?이미지 검색에는 없음 — 기준이 사진이었으므로
page.indexnumber1부터 시작
page.sizenumber요청한 크기가 아니라 실제로 돌아온 개수
page.totalnumber?목록 전체의 항목 수. 마켓플레이스가 밝힌 정확한 값 그대로이며, 반올림된 수치("260,000+")라면 빠집니다. 전부 받을 수 있다는 약속은 아닙니다. 다음 페이지가 없으면 거기서 끝납니다
itemsSearchItem[]
cursorstring?불투명 값. 커서로 페이지를 넘기는 마켓플레이스만 내보냄
facetsFacet[]?이 질의에 대한 마켓플레이스 자체의 필터 레일 — 아래 참고

Facet

이 질의에 대한 사이트 자체의 필터 레일입니다. 없으면 레일이 돌아오지 않은 것이고, []는 이 질의에 레일이 없다는 뜻입니다.

필드타입비고
idstring그룹의 키: brand, sock_rise
namestring표시 이름: "Brand", "Sock Rise"
kindselect | rangeselect는 값을 나열하고, range는 범위를 밝힘
values[]FacetValue{value, name, count?, selected?}
min maxnumber?range용. 금액 범위는 Money.amount와 같은 단위
maps_tostring?같은 개념을 다루는 타입 있는 필터, 예: brands

value는 facets 요청 파라미터로 그대로 돌려보내는 값입니다. 내부 id가 아니라 사람이 읽는 표시 이름입니다.

maps_to가 있으면 그 타입 있는 파라미터를 쓰세요. 모든 마켓플레이스에서 같은 것을 뜻합니다.

SearchItem과 Product

둘 다 하나의 아이템 형태를 확장하므로, 한 필드는 결과 카드에서나 상품 페이지에서나 같은 것을 뜻합니다. Product는 상세 화면에만 있는 것을 더합니다.

둘이 공유하는 필드

필드타입비고
marketplace id titlestring항상 있음
urlstring?리스팅 자체의 페이지
priceMoney?구매자가 오늘 지불하는 금액
listPriceMoney?할인 기준이 된 취소선 가격
conditionstring?표기된 그대로: "Pre-Owned", "Used: Very Good", "New"
conditionNotestring?그 상태에 대한 판매자 자신의 설명
unitPriceobject?{price, unit} — "$1.00 / count"
brandstring?사양 표에서 끌어올린 값. "Brand" 행은 attributes에 반복되지 않음
identifiersobject?{gtin?, mpn?, model?} — 바코드(UPC/EAN/ISBN), 부품/모델 번호, 모델명
ratingRating?{average, count?, distribution?, grouped?}
sellerSeller?{id?, name?, url?, rating?, country?, detailedRatings?, positivePercent?} — 아래 참고
salesCountnumber?정규화된 판매 수량. 마켓플레이스가 반올림하므로 하한값
salesLabelstring?문구 그대로: "3,000+ sold"
inStockboolean?
stocknumber?판매 가능 수량. 마켓플레이스가 숫자로 밝히는 경우
statusstring?표기된 그대로의 재고 문구: "Only 3 left in stock"
deliveryDeliveryOption?기본 배송 방식 — 아래 참고
shippingOptionsDeliveryOption[]?표시되거나 견적된 모든 옵션, 기본 옵션이 처음
returnsPolicystring?
badgesstring[]머천다이징 배지: ["Amazon's Choice"]
promotionsstring[]할인: ["5% off coupon applied"]
paymentOptionsstring[]?결제 방법: ["Pay in 4 payments at 0% interest"]
listingFormatstring?판매 방식: "Buy It Now", "Auction"
groupIdstring?이 상품이 속한 변형 패밀리
attributesAttribute[]{name, value} 쌍 — 사양 표에서 타입 있는 필드로 끌어올린 행을 뺀 나머지
offersSummaryobject?{count?, fromPrice?, url?} — 목록이 아니라 요약
priceRangeobject?Money로 된 {min, max} — 마켓플레이스가 밝힌 변형 간 가격 폭("$12.99 – $24.99"). 일부 변형만 보고 계산하지 않습니다
endsAtstring?현재 조건의 판매가 끝나는 시각. 경매 마감이나 기간 한정 딜의 종료. ISO 8601
purchaseLimitnumber?구매자 한 명이 이 상품을 주문할 수 있는 최대 수량("1인 2개 한정")
listedAtstring?리스팅이 마켓플레이스에 처음 게시된 날짜(수집 시각이 아님). ISO 8601 날짜 또는 시각
deliveryToobject?{label?, country?, postalCode?} — delivery와 shippingOptions의 배송 약속이 기준으로 삼은 배송지
extobject?공통 필드에 담을 곳이 없는, 그 마켓플레이스에만 있는 사실 — 아래 참고

Money는 {currency, amount}이며 amount는 십진 주단위입니다.

ext는 최후의 수단입니다. 같은 뜻의 공통 필드가 있으면 언제나 그 필드에 담고, 다른 마켓플레이스에는 없는 사실만 그 마켓플레이스의 이름 그대로 ext에 담습니다. 상품, 변형, 판매자, 오퍼, 리뷰, 리뷰 페이지, 카테고리 노드에 붙을 수 있습니다.

DeliveryOption

모든 배송이 쓰는 하나의 형태입니다 — delivery, shippingOptions의 각 항목, 오퍼의 delivery, ShippingQuote의 각 옵션.

필드타입비고
estimatestring?표기된 그대로의 도착 예상: "FREE delivery Wednesday, October 28"
costMoney?배송비
freeboolean?마켓플레이스가 무료배송이라고 밝힘
minDays maxDaysnumber?운송 기간(일), 숫자로 밝히는 경우
handlingDaysnumber?판매자가 발송하기까지의 일수
guaranteedDaysnumber?마켓플레이스 자체의 배송 보장 일수
cutoffstring?이 옵션의 주문 마감 카운트다운: "Order within 3 hrs 22 mins"
conditionstring?적용 조건: "on orders shipped by Amazon over $35"
availableboolean?false — 요청한 지역으로는 배송하지 않음
membershipstring?이 옵션에 필요한 멤버십(예: "Prime"). 없으면 모든 구매자에게 해당
minimumOrderMoney?cost/free가 적용되는 최소 주문 금액 — Amazon의 "on $35 of items"
carrier codestring?마켓플레이스가 부르는 배송 서비스
trackingboolean?
shipsFromstring?출고 국가

cost가 없는 것은 무료가 아니라 밝혀지지 않은 것입니다. 무료는 free: true입니다. available: false는 답이며, 결코 0원 배송비가 아닙니다. 마켓플레이스가 보여 준다면 delivery는 언제나 모든 구매자에게 해당하는 옵션입니다 — 멤버 전용 무료 배송(Prime)은 membership과 함께 shippingOptions에 있고, 모두의 조건으로 표시되지 않습니다.

Seller

필드타입비고
id name urlstring?
ratingRating?판매자 피드백 점수. 상품의 rating과 같은 형태
countrystring?판매자 소재 국가
detailedRatingsobject?{itemAsDescribed?, communication?, shippingSpeed?}, 마켓플레이스의 0–5 척도
positivePercentnumber?판매자 피드백 중 긍정 평가의 비율, 0–100 퍼센트("99.8% positive"). 별점이 아닙니다

rating.grouped: true는 이 집계가 요청한 하나의 변형이 아니라 변형 패밀리 전체를 다룬다는 뜻입니다. 이 값이 없다는 것은 묶여 있는지 알 수 없다는 뜻이며, "변형별임이 확인되었다"보다 약한 진술입니다.

SearchItem에만 있는 필드

필드타입비고
imageUrlstring?카드의 대표 이미지
sponsoredboolean?자연 검색 결과가 아니라 유료 노출
variantCountnumber?카드에 적힌 조합 수("+12 colors"). product는 이를 variants로 나열합니다
categoryCategoryStep[]?분류 체계에서 이 결과의 위치, 말단이 마지막. 카드에는 보통 말단만 있습니다
matchScorenumber?이미지 검색 전용. 보낸 사진과 얼마나 닮았는지를 0–1로 나타냄(1 = 마켓플레이스의 최상위 일치). 같은 결과 안에서만 비교할 수 있습니다

Product에만 있는 필드

필드타입비고
descriptionstring?HTML일 수 있음
featuresstring[]페이지 순서대로의 불릿 셀링 포인트
mediaobject{images: string[], videos: []}
categoryCategoryStep[]전체 경로, 말단이 마지막. 각 단계는 id, name, 또는 둘 다를 가짐
optionsOption[]축: 색상, 사이즈
variantsVariant[]조합들 — 아래 참고
offersOffer[]마켓플레이스가 열거하는 경우의 경쟁 판매자
subscriptionPriceMoney?Subscribe & Save 등
reviewSummarystring?마켓플레이스가 생성한 요약
reviewAspectsarray항목별 감성: {name, mentions?, positive?, negative?}
boxContentsstring[]구성품
brandUrl fulfilledBy replacedBystring?
packageobject?발송 포장: {lengthCm?, widthCm?, heightCm?, weightKg?, soldInLot?}
wholesaleboolean?대량 구매가를 제공하는 리스팅 (variants[].bulkPrice 참고)
reviewsReview[]상품 페이지 자체가 싣고 있는 상위 리뷰
includedstring[]?요청한 확장 중 실제로 가져온 것

변형, 그리고 반드시 확인할 한 가지

세상에는 두 가지 변형 모델이 있고, 그 둘을 가르는 것은 한 필드입니다.

  • variant.productId가 있음 — 그 조합 자체가 조회 가능한 상품입니다. 그 id를 product에 그대로 넘기세요. Amazon이 이 방식입니다.
  • variant.productId가 없음 — 그 조합은 이 상품 안에서만 존재하며, 필요한 것은 이미 다 받았습니다. Shopify·Walmart·AliExpress가 이 방식입니다.

어느 마켓플레이스와 이야기하는지 알 필요는 없습니다. 필드를 확인하세요.

변형 필드타입비고
idstring마켓플레이스가 그 조합에 붙인 id
productIdstring?있으면 ⇒ product로 조회 가능 (위 참고)
skustring?판매자 자신의 재고 관리 코드
price listPriceMoney?
bulkPriceMoney?대량 구매 시 단가. price와 다를 때만
purchaseLimitnumber?구매자 한 명이 주문할 수 있는 최대 수량
taxIncludedboolean?price에 세금이 포함되었는지
inStockboolean?
stocknumber?이 변형의 판매 가능 수량
identifiersobject?{gtin?, mpn?, model?} — 변형별로 공개된 경우, 이 조합의 바코드와 부품 번호
weightKgnumber?이 조합의 무게(킬로그램)
selectionsarray{optionId, valueId, optionName, valueName} — 어떤 옵션 값의 조합인지

Offer

마켓플레이스가 열거하는 경우의 경쟁 판매자: {seller?, price, listPrice?, condition?, conditionNote?, fulfilledBy?, inStock?, status?, delivery?}. 더 싸지만 더 늦게 배송되는 오퍼는 다른 오퍼이므로 delivery는 DeliveryOption입니다.

ReviewPage

{
  "marketplace": "walmart",
  "productId": "996425081",
  "summary": { "average": 4.6, "count": 1204, "distribution": { "5": 78 } },
  "page": { "index": 1, "size": 10, "total": 990 },
  "reviews": [ /* Review */ ],
  "grouped": true
}
필드타입비고
summaryRating?reviews를 뽑아낸 집합이 무엇이든 그것을 설명함
page.totalnumber?마켓플레이스가 밝힌, 목록에 있는 리뷰 수. 모든 평점을 세는 summary.count보다 적을 수 있음(Walmart는 글이 있는 리뷰만 나열). 0은 리뷰가 없다는 뜻
reviews[]Review{id?, author?, rating, title?, body?, date?, country?, verified?, helpfulCount?, variant?, images[], incentivized?, reply?}
groupedboolean?이 상품만이 아니라 패밀리 전체의 리뷰라는 뜻
cursorstring?

리뷰가 없는 상품

명시적인 0과 함께 200으로 답하므로, "리뷰 0건"을 자신 있게 표시할 수 있습니다.

{ "marketplace": "shopify", "productId": "shop.com/handle", "page": { "index": 1, "size": 0, "total": 0 }, "reviews": [] }

page.total: 0은 상품에 정말로 리뷰가 하나도 없을 때만 보냅니다. 상품에 리뷰가 있는데 가져올 수 없다는 증거가 있다면 — 개수나 평점이 보고되거나 상품 페이지에 리뷰가 보인다면 — 0이 아니라 에러가 돌아옵니다. 따라서 빈 reviews 배열은 두 가지 중 하나를 뜻하며, 어느 쪽인지는 page.total이 말해 줍니다.

page.total의미
0이 상품에는 리뷰가 없음
그 밖의 값 (숫자이거나 없음)리뷰의 끝을 지나 페이지를 넘긴 것

0으로 답할 때는 summary가 없습니다. 보고할 평균이 없기 때문입니다.

grouped는 부가 정보가 아니라 반드시 밝혀야 할 사실입니다. 판매자는 한 색상의 페이지에 패밀리 전체의 리뷰 9,000건으로 답할 수 있고, 리뷰 자체에는 그런 표시가 전혀 없습니다 — 묶인 페이지에서 "이 상품의 평점"을 계산하면 패밀리의 평점이 나옵니다.

review.incentivized: true는 마켓플레이스가 대가(무료 제품, 샘플, 보상)를 받고 쓴 리뷰로 표시한 경우입니다. 없으면 표시가 없다는 뜻일 뿐, "대가 없이 썼다"는 뜻은 아닙니다. review.reply는 판매자의 공개 답글이며 {body, date?, author?} 형태입니다.

review.variant는 마켓플레이스가 밝히는 경우 그 리뷰가 어느 형제 상품에 쓰였는지를 알려줍니다. 묶인 페이지 안에서 variant가 비어 있다면 마켓플레이스가 말하지 않았다는 뜻이지, "요청한 상품의 것"이라는 뜻은 결코 아닙니다.

page.total과 summary.count는 세는 대상이 다릅니다. 어떤 상품은 평점 7,654건을 표시하면서 리뷰 목록에는 글이 있는 리뷰 990건만 담고 있을 수 있습니다. count는 상품에 대해, total은 목록에 대해 참입니다. 어느 쪽도 모든 리뷰를 끝까지 받을 수 있다는 약속은 아닙니다. 마켓플레이스마다 페이지를 넘길 수 있는 깊이에 한도가 있고, 목록은 cursor가 끝나는 곳에서 끝납니다.

CategoryList

categories가 돌려주는 것: 하나의 평평한 목록이며, 모든 노드가 자신의 부모를 밝힙니다. 중첩이 아니라 평평한 이유는 흔한 요청이 "분류 전체"이고, 중첩하면 전송량이 커지고 페이지로 나누기도 어려워지기 때문입니다. 트리를 다시 만드는 일은 parentId 기준 groupBy 한 번입니다.

{
  "marketplace": "aliexpress",
  "path": [ { "id": "200000345", "name": "Women's Clothing" } ],
  "categories": [
    { "id": "349", "name": "Skirts", "parentId": "200000345" },
    { "id": "201531101", "name": "Matching Sets", "parentId": "200000345" }
  ]
}
필드타입비고
marketplacestring
pathCategoryStep[]요청한 카테고리의 조상들, 말단이 마지막. 루트에서는 비어 있음
categoriesCategoryNode[]

CategoryNode

필드타입비고
idstring되돌려 넘기는 id — 내려갈 때는 categories에, browsable이 허용하면 browse에
namestring
parentIdstring?루트에는 없음
hasChildrenboolean?없다는 것은 "아니오"가 아니라 알 수 없음 — 대부분의 마켓플레이스는 밝히지 않음
browsableboolean?아래 참고
productCountnumber?마켓플레이스가 밝히는 경우에만. 페이지에서 세지 않음

이 스키마의 대부분과 달리 id와 name은 둘 다 필수입니다. 카테고리 목록은 무언가를 하기 위해 존재합니다. id가 없는 항목은 쓸 수 없고, 이름이 없는 항목은 읽을 수 없습니다.

browsable — 특히 주의해서 읽을 값

없으면 가능하다는 뜻이고, 그것이 일반적인 경우입니다. false는 실재하고 탐색할 수 있지만 뒤에 상품 목록 페이지가 없는 단계를 표시합니다.

여기 있는 모든 마켓플레이스에 그런 단계가 있으며, 예외적인 경우가 아닙니다.

나열할 수 없는 것
Amazon부문 (browse 노드가 아니라 스토어 슬러그이므로)
Walmart일부 부문 — Electronics는 상품을 나열하지만 Toys는 그리드가 없는 허브

그런 단계를 만나면 categories로 한 단계 더 내려가세요. 거기 있는 id들은 나열됩니다.

categories에 browsableOnly=true 를 넘기면 browse가 받아들이는 id만 돌아옵니다 — 비용과 400이 나는 조건은 마켓플레이스 개요를 보세요.

이 플래그를 믿을 필요는 없습니다

browsable은 확인된 경우에만 채워지므로, 나열할 수 없는 단계가 이 필드 없이 — 즉 낙관적으로 — 돌아올 수 있습니다. Walmart에서는 피할 수 없습니다. Electronics(3944)와 Toys(4171)는 둘 다 자식이 있는 부문인데 하나는 상품을 나열하고 다른 하나는 나열하지 않으며, 분류 정보만으로는 구별할 방법이 없습니다.

그래서 browse가 직접 말해 줍니다. 상품 목록 페이지가 없는 카테고리를 요청하면 빈 목록이 아니라

{ "success": false, "error": { "code": "CATEGORY_NOT_BROWSABLE", "message": "Walmart category 4171 is a navigation hub…" } }

가 돌아옵니다. 빈 목록은 품절된 카테고리도 돌려주는 값이어서, "자식으로 내려가라"와 "여기에는 팔 것이 없다"를 구별할 방법이 없기 때문입니다. 이 코드는 같은 id로 categories를 부르고 그 자식 중 하나를 나열하라는 지시로 받아들이세요. 요청의 실패가 아니라 400이며, 재시도해도 답은 달라지지 않습니다.

ShippingQuote

shipping이 돌려주는 것: 한 상품을 한 지역으로 보내는 모든 방법, 가장 저렴한 것부터.

{
  "marketplace": "aliexpress",
  "productId": "1005012143419303",
  "skuId": "12000057632572864",
  "region": "US",
  "quantity": 1,
  "available": true,
  "options": [
    { "carrier": "YunExpress Standard Shipping", "cost": { "currency": "USD", "amount": 3.13 },
      "free": false, "minDays": 7, "maxDays": 15, "guaranteedDays": 60,
      "estimate": "Oct. 30 - Nov. 07", "tracking": true, "shipsFrom": "CN" }
  ]
}

options는 DeliveryOption입니다. 옵션 없이 available: false이면 마켓플레이스가 그 지역으로 배송하지 않는다는 뜻이며, 에러가 아니라 답입니다.

에러

{ "success": false, "error": { "code": "INVALID_PARAMS", "message": "…" } }
상태코드의미
400INVALID_PARAMS파라미터가 없거나, 형식이 잘못되었거나, 이 마켓플레이스가 받지 않는 것
400CATEGORY_NOT_BROWSABLE카테고리는 있으나 상품 목록 페이지가 없음 — categories로 내려가 자식을 나열하세요
404NOT_FOUND그런 id가 없음
404UNKNOWN_MARKETPLACE / UNKNOWN_OPERATIONGET /v1/marketplaces를 확인하세요
429레이트 리밋Retry-After를 포함
501UNSUPPORTED데이터는 있으나 이 API로는 제공되지 않음 — 요청의 문제가 아님
502업스트림마켓플레이스를 사용할 수 없었음. 재시도하세요

501이 404가 아닌 것은 의도적입니다. "이 리뷰는 제공되지 않습니다"가 "이 상품에는 리뷰가 없습니다"로 읽혀서는 안 됩니다.

이 페이지 내용