SiteChat용 카탈로그 업로드 방법

A secure merchant key unlocks catalog upload APIs that send products and images into SiteChat search and chat

자체 웹사이트에서 SiteChat을 사용 중이라면(Shopify 스토어가 아닌 경우), 구조화된 상품 레코드를 Shoply로 전송하여 쇼핑객이 SiteChat 채팅과 상품 검색에서 이를 찾을 수 있게 할 수 있습니다.

이 엔드포인트들은 Shoply Merchant API의 나머지 기능과 동일한 Settings → Store owner API secret를 사용하며, SiteChat 관리자 Product Catalog 페이지는 로그인한 관리자 액세스 토큰으로 이를 호출할 수 있습니다. Shopify 스토어는 계속해서 Shopify를 통해 카탈로그 데이터를 동기화합니다. 이 업로드 라우트는 이미 Magento 카탈로그를 동기화하고 있지 않은 SiteChat 계정에서만 사용할 수 있습니다.

SiteChat 관리자에서 Product Catalog를 열면 Products, Collections, Inventory가 있는 Shopify 스타일 영역이 열립니다. 여기서 상품을 추가하거나 수정하고, CSV/Excel을 가져오고, 상품을 수동 컬렉션으로 그룹화하고, 재고 수량을 조정할 수 있습니다.

어떤 API를 사용할 수 있나요?

APIMethod기능
/merchant/products/batchPOST한 번의 요청으로 최대 50개 상품 생성 또는 전체 교체
/merchant/productsPOST단일 상품 생성 또는 전체 교체
/merchant/productsGETsource와 external_id로 가져온 단일 상품 조회
/merchant/productsPATCH단일 상품 부분 업데이트; variants 또는 metafields를 교체하려면 GET에서 받은 버전이 필요
/merchant/productsDELETE상품 소프트 아카이브(status=archived)
/merchant/products/listGET관리자 테이블용으로 가져온 상품 페이지 조회
/merchant/products/imagesPOST상품 이미지 업로드 및 공개 HTTPS URL 수신
/merchant/collectionsGET / POST수동 컬렉션 목록 조회 또는 생성
/merchant/collections/{id}GET / PUT / DELETE단일 컬렉션 조회, 교체 또는 아카이브
/merchant/inventoryGET재고 행(상품 및 variant 수량) 목록 조회
/merchant/inventory/adjustPOST상품 또는 variant의 추적 재고 수량 설정

상품 저장이 성공하면 Shoply에 스토어 인덱스 재구성을 요청합니다. 이 재구성이 완료될 때까지 응답에는 index_status: "pending"가 보고됩니다. 이후 게시된 상품은 상품 인덱스와 SiteChat 채팅에서 사용하는 지식 모두에 나타납니다.

누가 이 API를 사용할 수 있나요?

  • 스토어는 SiteChat 계정이어야 합니다(app_platform이 SiteChat).
  • Shopify 스토어 키(*.myshopify.com 도메인 포함)는 거부됩니다.
  • 인증은 요청의 정확한 store_key에 대해 유효한 스토어 소유자 API 시크릿 또는 SiteChat 관리자 액세스 토큰을 사용해야 합니다.
  • Shopify Admin API 토큰으로는 이 라우트를 호출할 수 없습니다.

소유자 시크릿 생성 또는 교체는 다른 Merchant API 연동과 동일한 방식으로 수행합니다: Shoply Merchant API 사용 방법. SiteChat 관리자 콘솔에서는 Product Catalog를 열어 직접 시크릿을 관리하지 않고도 상품, 컬렉션, 재고를 관리하거나 CSV 또는 Excel 파일을 가져올 수 있습니다.

인증은 어떻게 동작하나요?

서버 커넥터에서

신뢰할 수 있는 백엔드에서 HTTPS를 통해 API를 호출하세요. Authorization 헤더에 JSON 문자열을 넣습니다:

json
{ "store_key": "YOUR_SITECHAT_STORE_KEY", "store_owner_api_secrete": "YOUR_STORE_OWNER_API_SECRET" }

store_owner_api_secrete는 공개 필드 이름이며, 역사적인 철자를 그대로 포함합니다. store_owner_api_secret도 허용됩니다. 이것은 Bearer 토큰이 아닙니다.

SiteChat 관리자에서

Product Catalog 페이지는 대신 로그인한 관리자 세션을 전송합니다:

json
{ "store_key": "YOUR_SITECHAT_STORE_KEY", "admin_auth_token": "YOUR_SITECHAT_ACCESS_TOKEN" }

access_token은 admin_auth_token의 별칭으로 허용됩니다.

store_key 쿼리 파라미터는 헤더와 일치해야 합니다. 소유자 시크릿은 서버 측 환경 변수에만 보관하세요. 절대 스토어프론트 스크립트, URL 또는 공개 저장소에 넣지 마세요.

bash
export SHOPLY_STORE_KEY="your-sitechat-store-key" export SHOPLY_STORE_OWNER_API_SECRETE="shoply_owner_key_example123.secret-value"

상품은 어떻게 업로드하나요?

POST https://api.shoplyai.ai/merchant/products/batch?store_key=YOUR_SITECHAT_STORE_KEY

json
{ "source": "woocommerce-main", "products": [ { "external_id": "123", "title": "Trail shoes", "description": "Water-resistant hiking shoes.", "url": "https://example.com/products/trail-shoes", "currency": "USD", "price": "49.95", "original_price": "59.95", "available": true, "status": "published", "images": ["https://example.com/images/trail-shoes.jpg"], "categories": ["Footwear"], "metafields": {"material": "Leather"}, "variants": [ { "external_id": "124", "title": "Brown / 42", "sku": "TRAIL-BR-42", "price": "49.95", "available": true, "metafields": {"color": "Brown", "size": "42"} } ] } ] }

필수 필드와 규칙

  • 필수 상품 필드: external_id, title, url, currency, price, available. status의 기본값은 published입니다.
  • 선택적 재고: 상품 또는 variant에 tracks_inventory를 true로 설정하고 0 이상의 quantity를 지정하세요. 그러면 Shoply는 재고(quantity > 0)를 기반으로 구매 가능 여부를 계산하고, 관리자 목록용 total_inventory를 저장합니다.
  • source는 카탈로그 연결 이름을 나타냅니다(반드시 플랫폼일 필요는 없음). woocommerce-main 같은 안정적인 이름을 사용하세요. 허용 문자: 소문자, 숫자, 밑줄, 하이픈, 최대 64자. 관리자에서 폼으로 생성한 상품의 기본 source는 admin입니다.
  • 상품 식별자는 store, source, external ID의 조합입니다. batch 및 단일 POST 업서트는 부분 수정이 아니라 전체 교체입니다. 즉, 생략된 선택 필드는 비워집니다. 부분 업데이트에는 PATCH를 사용하세요.
  • 배치에는 1~50개의 상품을 포함할 수 있으며, 요청 크기는 최대 2,000,000바이트입니다. 각 상품의 검증된 JSON은 최대 128,000바이트이며, variants는 최대 250개까지 허용됩니다.
  • 가격에는 10진수 문자열 사용을 권장합니다. 통화는 세 글자의 대문자 코드여야 합니다.
  • 상품 및 이미지 URL은 HTTP(S)여야 합니다. 가져오기 과정에서 해당 URL을 자동으로 가져오지는 않습니다.
  • 검색 가능한 상품 사양에는 공개 metafields를 사용하세요(SiteChat에서 Shopify 상품 metafields에 대응하는 개념). 값은 일반 문자열이어야 합니다. 레거시 attributes도 별칭으로 허용됩니다. 비공개 상품 metadata는 더 이상 지원되지 않습니다.
  • draft 및 archived 상품은 다음 게시 인덱스에서 제외됩니다. 품절된 published 상품은 구매 가능 여부 정보가 붙은 채 계속 인덱싱됩니다.
  • 수동 컬렉션에는 제목, 설명, 상태, 그리고 상품 소속 목록(source + external_id)이 저장됩니다. 이번 릴리스에서는 스마트/규칙 기반 컬렉션은 지원되지 않습니다.

성공 응답 예시

json
{ "results": [ { "external_id": "123", "product_id": "sitechat_product::woocommerce-main::<sha256-of-external-id>", "status": "stored" } ], "index_status": "pending", "index_revision": 1 }

Shoply는 쓰기 전에 전체 요청을 검증합니다. HTTP 200이더라도 일부 failed 결과와 함께 error: "storage_error"가 포함될 수 있습니다. 모든 결과를 확인하고 실패한 상품은 재시도하세요. index_status가 request_failed이면 저장은 성공했지만 인덱싱이 예약되지 않았다는 뜻이므로, 저장된 상품도 다시 시도해야 합니다.

Python 예시

python
import json import os import requests store_key = os.environ["SHOPLY_STORE_KEY"] headers = { "Authorization": json.dumps({ "store_key": store_key, "store_owner_api_secrete": os.environ["SHOPLY_STORE_OWNER_API_SECRETE"], }) } response = requests.post( "https://api.shoplyai.ai/merchant/products/batch", params={"store_key": store_key}, headers=headers, json={ "source": "custom", "products": [{ "external_id": "123", "title": "Trail shoes", "url": "https://example.com/products/trail-shoes", "currency": "USD", "price": "49.95", "available": True, }], }, timeout=60, ) response.raise_for_status() result = response.json() failed = [item["external_id"] for item in result["results"] if item["status"] != "stored"] if failed: raise RuntimeError(f"Products need retry: {failed}") if result["index_status"] == "request_failed": raise RuntimeError("Products saved, but retry the batch to request indexing")

가져온 상품은 어떻게 확인하나요?

GET https://api.shoplyai.ai/merchant/products?store_key=YOUR_SITECHAT_STORE_KEY&source=woocommerce-main&external_id=123

동일한 Authorization 헤더를 사용하세요. 응답에는 schema_version, updated_at, index_status, 그리고 정규화된 product가 포함됩니다. 레코드가 없으면 HTTP 404를 반환합니다.

백그라운드 인덱서가 재구성을 게시한 뒤에는 상품별 읽기 상태가 indexed, excluded(draft 또는 archived 상품의 경우), 또는 limit_exceeded가 됩니다. 관리자 목록 조회에는 GET /merchant/products/list를 사용하세요. 다음 게시 인덱스에서 상품을 제외하려면 DELETE /merchant/products로 소프트 아카이브하거나(status를 archived / draft로 설정해도 됨), 이번 릴리스에서는 하드 삭제는 지원되지 않습니다.

python
record = requests.get( "https://api.shoplyai.ai/merchant/products", params={ "store_key": store_key, "source": "woocommerce-main", "external_id": "123", }, headers=headers, timeout=30, ) record.raise_for_status() print(record.json()["index_status"], record.json()["product"]["title"])

variants 또는 metafields는 어떻게 안전하게 업데이트하나요?

PATCH에 variants, metafields, 또는 레거시 attributes 필드가 포함되는 경우 먼저 GET으로 상품을 읽으세요. 그 응답을 기반으로 수정 내용을 만들고, PATCH 본문에 최상위 updated_at 값을 expected_updated_at로 포함하세요. variants 또는 metafields를 비우는 경우에도 동일하게 적용됩니다.

python
current_response = requests.get( "https://api.shoplyai.ai/merchant/products", params={"store_key": store_key, "source": "woocommerce-main", "external_id": "123"}, headers=headers, timeout=30, ) current_response.raise_for_status() current = current_response.json() updated_metafields = {**current["product"].get("metafields", {}), "material": "Leather"} response = requests.patch( "https://api.shoplyai.ai/merchant/products", params={"store_key": store_key}, headers=headers, json={ "source": "woocommerce-main", "external_id": "123", "expected_updated_at": current["updated_at"], "metafields": updated_metafields, }, timeout=30, ) response.raise_for_status()

HTTP 409는 버전이 누락되었거나 다른 업데이트가 상품을 변경했다는 뜻입니다. 재시도하기 전에 최신 상품을 다시 읽고 의도한 변경사항을 다시 병합하세요. 이전 variants 목록에 새 버전을 붙이지 마세요. 그렇게 하면 더 최신 재고 값이 덮어써질 수 있습니다.

버전 없이 이 필드들을 PATCH하던 기존 연동은 업데이트가 필요합니다. 스칼라 값만 포함한 PATCH 요청, 전용 재고 조정, 그리고 POST 또는 배치 가져오기를 통한 전체 교체에는 이 필드가 필요하지 않습니다. 단, 전체 교체는 여전히 상품 전체를 교체합니다.

상품 이미지를 업로드할 수 있나요?

네. 이미지가 이미 지속적으로 접근 가능한 공개 HTTPS URL에 있다면, 그 URL을 상품의 images 목록이나 variant의 image 필드에 넣으면 됩니다. 공개 S3 HTTPS URL도 사용할 수 있습니다. 원시 s3:// 경로나 짧은 수명의 서명 URL은 사용할 수 없습니다.

파일을 Shoply에서 호스팅하게 하려면 백엔드에서 원시 이미지 바이트를 업로드하세요:

text
POST https://api.shoplyai.ai/merchant/products/images?store_key=YOUR_SITECHAT_STORE_KEY Content-Type: image/png

JSON, base64 또는 multipart form data가 아니라 원시 파일 바이트를 전송하세요. JPEG, PNG, WebP를 지원하며, 최대 10 MiB 및 2천만 픽셀까지 허용됩니다. 애니메이션 이미지는 지원되지 않습니다. Shoply는 이미지를 WebP로 변환하고 내장 메타데이터를 제거합니다.

python
with open("trail-shoes.png", "rb") as image_file: response = requests.post( "https://api.shoplyai.ai/merchant/products/images", params={"store_key": store_key}, headers={**headers, "Content-Type": "image/png"}, data=image_file, timeout=60, ) response.raise_for_status() image_url = response.json()["url"]

HTTP 201은 url, content_type, size_bytes, width, height를 반환합니다. 이미지만 업로드한다고 해서 상품에 자동 연결되거나 인덱싱이 요청되지는 않습니다. 반환된 URL을 완전한 상품 데이터에 포함시킨 뒤 /merchant/products/batch를 통해 다시 제출하세요.

같은 계정에서 동일한 이미지를 다시 업로드하면 기존 URL을 재사용합니다. 이미지가 변경되면 새 URL이 발급됩니다. 이번 릴리스에는 이미지 삭제 엔드포인트가 없습니다.

상품은 언제 채팅과 검색에 표시되나요?

배치 저장이 성공하면 Shoply는 백그라운드 인덱스 재구성을 요청합니다. 작업자가 새 인덱스를 게시할 때까지 응답에는 index_status: "pending"가 표시됩니다. 완료 시간에 대한 고정된 보장은 없습니다.

  • Published 상품은 상품 검색과 SiteChat 채팅에서 사용하는 지식 모두에 반영됩니다.
  • Draft 및 archived 상품은 다음 재구성에서 제외됩니다.
  • 인덱싱이 예약되지 않았다면(index_status: "request_failed"), 이미 저장에 성공한 상품들에 대해 배치를 다시 시도하세요.

어떤 오류를 예상해야 하나요?

HTTP status의미
403시크릿 누락, 유효하지 않음, 만료됨, 취소됨, 잘못된 스토어, 또는 SiteChat이 아닌 계정
404요청한 가져온 상품이 존재하지 않음
413요청 또는 이미지가 크기 제한 초과
415이미지 업로드에 지원되지 않는 Content-Type 사용
422잘못된 필드, 중복 ID, 애니메이션 또는 과대 이미지, 또는 모델 제한 초과
503일시적인 저장 실패

시크릿은 90일 후 만료됩니다. 만료 전에 Settings → Store owner API secret에서 새 시크릿을 생성하고, 더 이상 필요하지 않은 시크릿은 취소하세요. 동일한 시크릿으로 Merchant API 가이드에 문서화된 분석, 대화, 지식 엔드포인트도 호출할 수 있습니다.

연동 관련 도움이 필요하면 Shoply AI에 문의하세요.