상품 등록
accessTokenAuthorizationBearer <token>Sayren 계정 토큰(로그인 토큰 또는 개인 액세스 토큰, RFC 9068 JWT)
X-Store-Id*string대상 상점 id. 토큰의 상점 범위 밖이면 403 STORE_NOT_GRANTED, 없으면 400 STORE_ID_REQUIRED다
idempotency-key?string멱등키. 같은 키로 다시 보내면 처음 결과를 돌려주고 한 번만 적용한다. 재시도에는 같은 키를 쓴다
length <= 255application/json- body
name*string1 <= length <= 100categoryId*string1 <= lengthsalePrice*integer판매가. 원(KRW) 단위 정수, 최소 10원, 10원 단위
1010 <= valueoriginalPrice?integer정가(할인 전 표시 가격). 원 단위. 비교용 표시 값이고 결제 금액은 판매가(즉시할인이 있으면 할인 적용가)다. 스토어프론트가 판매가보다 클 때 취소선·할인율로 보인다. 즉시할인은 PUT /v1/products/{productId}/discount로 따로 정한다
pointEarnRateBp?|구매 적립률 덮어쓰기(만분율, 100 = 1%, 0~3000, 이슈 #93). null이면 상점 기본 적립률, 0이면 이 상품은 적립하지 않는다. 결제 확정 시점 값이 주문상품에 남는다. 전체 수정(PUT)에서 생략하면 지금 값을 유지한다
0 <= value <= 3000description?stringdescriptionJson?string|nullimages?array<>상품 이미지 주소 목록. 첫 장이 대표 이미지(썸네일)다
1 <= items <= 10attributes?array<>refundPolicy?|상품별 환불 정책 — 준 항목만 상점 기본을 덮어쓴다. null이나 생략은 상점 기본을 그대로 쓴다는 뜻이다. 반품·교환 배송비(returnDeliveryFee·exchangeDeliveryFee)는 배송 상품에만 줄 수 있다(다른 유형은 400)
optionGroups?array<>items <= 3[]variants*array<>1 <= items <= 500addonGroups?array<>추가 선택 옵션명 전체(이슈 #59). 주면 통째로 바꾸고, 생략하면 등록은 없음·수정은 현재 값을 그대로 둔다. 끄려면 빈 배열을 보낸다. 기능이 열리기 전에는 비어 있지 않은 값이 409 OPTION_ADDONS_UNAVAILABLE이다
items <= 5customInputs?array<>직접 입력 항목 전체(이슈 #59). 배열 순서가 표시 순서다. 주면 통째로 바꾸고, 생략하면 등록은 없음·수정은 현재 값을 그대로 둔다. 항목명에 개인정보성 단어(CUSTOM_INPUT_FORBIDDEN_WORDS)가 있으면 400 CUSTOM_INPUT_LABEL_FORBIDDEN, 기능이 열리기 전에는 비어 있지 않은 값이 409 OPTION_ADDONS_UNAVAILABLE이다
items <= 5optionSort?string옵션 값 정렬 순서(이슈 #59). 서버가 이 순서로 값의 sortOrder를 다시 매긴다. 생략하면 등록은 REGISTERED, 수정은 현재 값이다
"REGISTERED""NAME""PRICE_ASC""PRICE_DESC"sourceOptionSetIds?array<string>쓰기 전용(이슈 #59) — 이 저장으로 옵션을 불러온 저장 옵션 id. 상품 저장과 같은 트랜잭션에서 불러오기 기록을 남겨 저장 옵션의 importedProductCount를 센다. 없는 id는 무시한다
items <= 10inventoryMode?string재고 방식(이슈 #118). DATED면 날짜별 달력(PUT /products/{id}/calendar)이 재고·가격이고 주문서 줄은 숙박 기간(stay)을 받는다. 생략하면 등록은 STANDARD, 수정은 지금 값. 판매 이력이 있으면 바꿀 수 없다(409 INVENTORY_MODE_LOCKED)
"STANDARD""DATED"flowId?|주문 흐름(이슈 #115) — 게시한 흐름 id. null이면 카테고리의 흐름(없으면 기본 흐름)을 쓴다. 생략하면 등록은 null, 수정은 지금 값. 게시하지 않은 흐름은 409 FLOW_NOT_PUBLISHED. 이미 결제된 주문은 결제 때 붙은 흐름을 그대로 따른다
fulfillment?|상품 이행. type이 유형이고 같은 이름의 키가 유형별 속성이다. 생략하면 배송 상품(기본값)이다. 배송 상품이 아닌 유형은 판매가 열린 유형만 등록할 수 있다(409 FULFILLMENT_TYPE_NOT_AVAILABLE)
성공
application/json- response
meta*data*error*|nullcurl -X POST 'https://api.sayren.io/v1/products' \ -H 'X-Store-Id: string' \ -H 'Content-Type: application/json' \ -d '{ "name": "오버핏 코튼 반팔 티셔츠", "categoryId": "cat_tshirt", "salePrice": 29000, "originalPrice": 39000, "description": "<p>도톰한 20수 코튼으로 만든 오버핏 반팔 티셔츠입니다.</p>", "images": [ "https://cdn.avarlabs.com/demo/tshirt-white.jpg" ], "fulfillment": { "type": "SHIPPING", "shipping": { "deliveryType": "CONDITIONAL_FREE", "deliveryFee": 3000, "conditionalFreeAmount": 50000 } }, "refundPolicy": { "returnDeliveryFee": 3000, "exchangeDeliveryFee": 6000 }, "optionGroups": [ { "name": "색상", "values": [ { "name": "화이트" }, { "name": "블랙" } ] }, { "name": "사이즈", "values": [ { "name": "M" }, { "name": "L" } ] } ], "variants": [ { "valueNames": [ "화이트", "M" ], "stockQuantity": 50 }, { "valueNames": [ "화이트", "L" ], "stockQuantity": 40 }, { "valueNames": [ "블랙", "M" ], "stockQuantity": 35 }, { "valueNames": [ "블랙", "L" ], "additionalPrice": 1000, "stockQuantity": 20 } ]}'{ "meta": { "status": 201, "code": "OK", "message": "OK", "isSuccess": true }, "data": { "productId": "prod_3f9a1c2b7d4e", "productNo": 482, "name": "오버핏 코튼 반팔 티셔츠", "status": "SALE", "salePrice": 29000, "discountedPrice": null, "stockQuantity": 145, "categoryId": "cat_tshirt", "thumbnailUrl": "https://cdn.avarlabs.com/demo/tshirt-white.jpg", "hasOptions": true, "updatedAt": "2026-09-24T10:30:00+09:00", "createdAt": "2026-09-24T10:30:00+09:00", "description": "<p>도톰한 20수 코튼으로 만든 오버핏 반팔 티셔츠입니다.</p>", "descriptionJson": null, "originalPrice": 39000, "images": [ "https://cdn.avarlabs.com/demo/tshirt-white.jpg" ], "attributes": [], "optionGroups": [ { "groupId": "og_color", "name": "색상", "sortOrder": 0, "values": [ { "valueId": "ov_white", "name": "화이트", "sortOrder": 0 }, { "valueId": "ov_black", "name": "블랙", "sortOrder": 1 } ] }, { "groupId": "og_size", "name": "사이즈", "sortOrder": 1, "values": [ { "valueId": "ov_m", "name": "M", "sortOrder": 0 }, { "valueId": "ov_l", "name": "L", "sortOrder": 1 } ] } ], "variants": [ { "variantId": "var_white_m", "variantNo": "482-01", "valueIds": [ "ov_white", "ov_m" ], "name": "화이트 / M", "additionalPrice": 0, "stockQuantity": 50, "sku": null, "barcode": null, "costPrice": null, "weightGram": null, "sellerCode": null, "usable": true }, { "variantId": "var_white_l", "variantNo": "482-02", "valueIds": [ "ov_white", "ov_l" ], "name": "화이트 / L", "additionalPrice": 0, "stockQuantity": 40, "sku": null, "barcode": null, "costPrice": null, "weightGram": null, "sellerCode": null, "usable": true }, { "variantId": "var_black_m", "variantNo": "482-03", "valueIds": [ "ov_black", "ov_m" ], "name": "블랙 / M", "additionalPrice": 0, "stockQuantity": 35, "sku": null, "barcode": null, "costPrice": null, "weightGram": null, "sellerCode": null, "usable": true }, { "variantId": "var_black_l", "variantNo": "482-04", "valueIds": [ "ov_black", "ov_l" ], "name": "블랙 / L", "additionalPrice": 1000, "stockQuantity": 20, "sku": null, "barcode": null, "costPrice": null, "weightGram": null, "sellerCode": null, "usable": true } ], "addonGroups": [ { "groupId": "og_wrap", "name": "선물 포장", "sortOrder": 1, "required": false, "values": [ { "valueId": "ov_wrap_box", "name": "상자 포장", "sortOrder": 1, "additionalPrice": 2000, "usable": true } ] } ], "customInputs": [ { "inputId": "cin_engrave", "label": "각인 문구", "placeholder": "영문 10자까지", "maxLength": 10, "required": false, "usable": true, "sortOrder": 1 } ], "optionSort": "REGISTERED", "fulfillment": { "type": "SHIPPING", "shipping": { "deliveryType": "CONDITIONAL_FREE", "deliveryFee": 3000, "conditionalFreeAmount": 50000, "shippingAddressId": "adr_ship_main", "returnAddressId": "adr_return_main" } }, "fulfillmentTypeLocked": true, "refundPolicy": { "returnDeliveryFee": 3000, "exchangeDeliveryFee": 6000 }, "discount": null, "sample": false }, "error": null}