v1 증분 인벤토리 업데이트

이 섹션에서는 시간에 민감한 피드 업데이트를 Google에 전송하는 방법을 설명합니다. 점진적 업데이트 API를 사용하면 거의 실시간으로 피드의 항목을 업데이트하고 삭제할 수 있습니다.

이 기능은 주로 긴급 휴업과 같이 예상할 수 없는 업데이트를 위한 것입니다. 일반적으로 점진적 업데이트 API를 통해 제출된 모든 변경사항은 1주일 이내에 게시되어야 하는 변경사항이어야 합니다. 변경사항을 즉시 반영할 필요가 없으면 일괄 업데이트를 대신 사용할 수 있습니다. 증분 업데이트는 5분 이내에 처리됩니다.

설정

증분 업데이트를 구현하려면 다음을 실행하세요.

  1. 프로젝트 만들기 및 설정에 설명된 단계에 따라 프로젝트를 만듭니다.
  2. 서비스 계정 설정에 설명된 단계에 따라 서비스 계정을 만듭니다. 서비스 계정에 '편집자' 역할을 추가하려면 프로젝트의 '소유자'여야 합니다.
  3. (선택사항이지만 권장됨) API를 호출할 때 OAuth 2.0을 쉽게 사용할 수 있도록 원하는 언어로 Google 클라이언트 라이브러리를 설치합니다. 아래에 포함된 코드 샘플은 이러한 라이브러리를 사용합니다. 그러지 않으면 OAuth 2.0을 사용하여 Google API에 액세스에 설명된 대로 토큰 교환을 수동으로 처리해야 합니다.

엔드포인트

Google에 업데이트를 알리려면 증분 업데이트 API에 HTTP POST 요청을 하고 업데이트 및 추가 페이로드를 포함합니다. 사용하는 인벤토리 스키마에 따라 요청을 수행할 엔드포인트가 결정됩니다.

v2 인벤토리

https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/TYPE/ENTITY_ID:push

v1 인벤토리

https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/ENTITY_ID:push

항목을 삭제하려면 사용 중인 인벤토리 스키마에 해당하는 다음 엔드포인트에 HTTP DELETE 요청을 수행합니다.

v2 인벤토리

https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/TYPE/ENTITY_ID?entity.vertical=FOODORDERING&delete_time=DELETE_TIME

v1 인벤토리

https://actions.googleapis.com/v2/apps/PROJECT_ID/entities/ENTITY_ID?entity.vertical=FOODORDERING&delete_time=DELETE_TIME

위의 요청에서 다음을 바꿉니다.

  • PROJECT_ID: 프로젝트 만들기 및 설정에서 만든 프로젝트와 연결된 Google Cloud 프로젝트 ID입니다.
  • TYPE (v2 인벤토리 스키마만 해당): 업데이트할 데이터 피드에 있는 객체의 항목 유형 (@type 속성)입니다.
  • ENTITY_ID: 페이로드에 포함된 항목의 ID입니다. 엔티티 ID를 URL로 인코딩해야 합니다.
  • DELETE_TIME(삭제 엔드포인트만 해당): 시스템에서 항목이 삭제된 시간을 나타내는 선택 필드입니다. 기본값은 요청이 수신된 시간입니다. 시간 값은 미래일 수 없습니다. 증분 호출을 통해 항목을 전송할 때는 항목 버전 관리에서도 삭제 호출의 경우 delete_time 필드를 사용합니다. 이 값의 형식을 yyyy-mm-ddTHH:mm:ssZ로 지정하세요.

예를 들어 v2 인벤토리 스키마를 사용하는 ID가 'delivery-provider-id'인 프로젝트가 있다고 가정하겠습니다. 레스토랑 항목 유형이 'MenuSection'이고 항목 ID가 'menuSection_122'인 레스토랑을 변경하려고 합니다. 데이터 업데이트를 위한 엔드포인트는 다음과 같습니다.

https://actions.googleapis.com/v2/apps/delivery-provider-id/entities/MenuSection/menuSection_122:push

동일한 항목을 삭제하려면 다음과 같이 HTTP DELETE API를 호출합니다.

https://actions.googleapis.com/v2/apps/delivery-provider-id/entities/MenuSection/menuSection_122?entity.vertical=FOODORDERING

샌드박스 요청

샌드박스 요청의 경우 위 엔드포인트의 안내를 따르되 /v2/apps/ 대신 /v2/sandbox/apps/에 요청합니다. 예를 들어 v2 인벤토리 스키마의 샌드박스 삭제 요청은 다음과 같이 구성됩니다.

https://actions.googleapis.com/v2/sandbox/apps/PROJECT_ID/entities/TYPE/ENTITY_ID?entity.vertical=FOODORDERING&delete_time=DELETE_TIME

업데이트 및 추가사항

일일 일괄 피드에는 이 API를 통해 제출된 변경사항도 포함되어야 합니다. 그렇지 않으면 일괄 업데이트가 증분 변경사항을 덮어씁니다.

페이로드

각 POST 요청에는 인벤토리 스키마에 나열된 항목 유형의 구조화된 데이터가 포함된 JSON 페이로드와 함께 요청 매개변수가 포함되어야 합니다.

JSON은 일괄 피드에서와 동일하게 표시되지만 다음과 같은 차이가 있습니다.

  • 페이로드 본문의 크기는 5MB를 초과하면 안 됩니다. 일괄 피드와 마찬가지로, 더 많은 데이터를 맞추기 위해 공백을 제거하는 것이 좋습니다.
  • 봉투는 다음과 같습니다.
{
  "entity": {
    "data":"ENTITY_DATA",
    "vertical":"FOODORDERING"
  },
  "update_time":"UPDATE_TIMESTAMP"
}

위의 페이로드에서 다음을 바꿉니다.

  • ENTITY_DATA: 문자열로 직렬화된 JSON 형식의 항목입니다. JSON-LD 항목은 data 필드에 문자열로 전달되어야 합니다.
  • UPDATE_TIMESTAMP (선택사항): 시스템에서 항목이 업데이트된 시점의 타임스탬프입니다. 시간 값은 미래일 수 없습니다. 기본 타임스탬프는 Google이 요청을 수신한 시점입니다. 증분 요청을 통해 항목을 전송할 때는 항목 버전 관리에서도 추가/업데이트 요청의 경우 update_time 필드를 사용합니다.

항목 업데이트

예 1: 음식점 업데이트

급하게 식당의 전화번호를 업데이트해야 한다고 가정해 보겠습니다. 업데이트에 전체 식당의 JSON이 포함됩니다.

다음과 같은 일괄 피드를 생각해 보세요.

{
  "@type": "Restaurant",
  "@id": "restaurant12345",
  "name": "Some Restaurant",
  "url": "https://www.provider.com/somerestaurant",
  "telephone": "+16501234567",
  "streetAddress": "345 Spear St",
  "addressLocality": "San Francisco",
  "addressRegion": "CA",
  "postalCode": "94105",
  "addressCountry": "US",
  "latitude": 37.472842,
  "longitude": -122.217144
}

그러면 HTTP POST에 의한 증분 업데이트는 다음과 같습니다.

POST v2/apps/provider-project/entities/Restaurant/restaurant12345:push
Host: actions.googleapis.com
Content-Type: application/ld+json
{
  "entity": {
    "data": {
      "@type": "Restaurant",
      "@id": "restaurant12345",
      "name": "Some Restaurant",
      "url": "https://www.provider.com/somerestaurant",
      "telephone": "+16501235555",
      "streetAddress": "345 Spear St",
      "addressLocality": "San Francisco",
      "addressRegion": "CA",
      "postalCode": "94105",
      "addressCountry": "US",
      "latitude": 37.472842,
      "longitude": -122.217144
    },
    "vertical": "FOODORDERING"
  }
}

예시 2: 메뉴 항목 가격 업데이트

메뉴 항목의 가격을 변경해야 한다고 가정해 보겠습니다. 예 1에서와 같이 업데이트에는 전체 최상위 항목 (메뉴)에 대한 JSON이 포함되어야 하고 피드는 v1 인벤토리 스키마를 사용합니다.

다음과 같은 일괄 피드를 생각해 보세요.

{
  "@type": "MenuItemOffer",
  "@id": "menuitemoffer6680262",
  "sku": "offer-cola",
  "menuItemId": "menuitem896532",
  "price": 3.00,
  "priceCurrency": "USD"
}

그러면 POST를 통한 증분 업데이트는 다음과 같습니다.

POST v2/apps/provider-project/entities/MenuItemOffer/menuitemoffer6680262:push
Host: actions.googleapis.com
Content-Type: application/ld+json
{
  "entity": {
    "data": {
      "@type": "MenuItemOffer",
      "@id": "menuitemoffer6680262",
      "sku": "offer-cola",
      "menuItemId": "menuitem896532",
      "price": 1.00,
      "priceCurrency": "USD"
    },
    "vertical": "FOODORDERING"
  }
}

항목 추가

항목을 추가하려면 인벤토리 업데이트를 사용하지 마세요. 대신 v2 인벤토리 스키마에 설명된 대로 일괄 피드 프로세스를 사용하세요.

항목 삭제

최상위 항목을 삭제하려면 약간 수정된 엔드포인트를 사용하고 요청에서 HTTP POST 대신 HTTP DELETE를 사용합니다.

HTTP DELETE를 사용하여 최상위 항목 내 하위 항목(예: 메뉴 내의 메뉴 항목)을 삭제하지 마세요. 대신 하위 항목 삭제를 관련 목록 또는 매개변수에서 하위 항목이 삭제되는 최상위 항목의 업데이트로 취급합니다.

예시 1: 최상위 항목 삭제

v1 인벤토리 스키마를 사용하는 피드에서 레스토랑을 삭제하려고 하는 상황을 생각해 보겠습니다. 서비스와 메뉴도 삭제해야 합니다.

ID가 'https://www.provider.com/spreadsheets/menu/nr'인 메뉴 항목의 샘플 엔드포인트:

DELETE v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fmenu%2Fnr?entity.vertical=FOODORDERING
Host: actions.googleapis.com

ID가 'https://www.provider.com/spreadsheets/nr'인 식당 항목의 샘플 엔드포인트입니다.

DELETE v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fnr?entity.vertical=FOODORDERING
Host: actions.googleapis.com

ID가 'https://www.provider.com/spreadsheets/service/nr'인 서비스 항목의 샘플 엔드포인트:

DELETE v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fservice%2Fnr?entity.vertical=FOODORDERING
Host: actions.googleapis.com
}

예시 2: 하위 항목 삭제

최상위 항목 내에서 하위 항목을 제거하려면 해당 필드에서 하위 항목이 삭제된 상태로 최상위 항목을 전송합니다. 다음 예에서는 피드가 v1 인벤토리 스키마를 사용한다고 가정합니다.

예를 들어 서비스 지역을 삭제하려면 areaServed 목록에서 삭제된 서비스 지역으로 서비스를 업데이트합니다.

POST v2/apps/delivery-provider-id/entities/https%3A%2F%2Fwww.provider.com%2Frestaurant%2Fservice%2Fnr:push
Host: actions.googleapis.com
Content-Type: application/ld+json
{
  "entity": {
    // Note: "data" is not serialized as a string in our example for readability.
    "data": {
      "@type": "Service",
      "provider": {
        "@type": "Restaurant",
        "@id": "https://www.provider.com/restaurant/nr"
      },
      "areaServed": [
        {
          "@type": "GeoCircle",
          "geoMidpoint": {
            "@type": "GeoCoordinates",
            "latitude": "42.362757",
            "longitude": "-71.087109"
          },
          "geoRadius": "10000"
        }
        // area2 is removed.
      ]
      ...
    },
    "vertical": "FOODORDERING"
  }
}

API 응답 코드

호출이 성공한다고 해서 피드가 유효하거나 정확하다는 뜻은 아니며, API가 호출되었다는 뜻일 뿐입니다. 호출이 성공하면 응답 본문이 비어 있는 상태로 HTTP 응답 코드 200이 수신됩니다.

{}

실패하면 HTTP 응답 코드는 200이 되지 않으며 응답 본문에 문제점을 표시합니다.

예를 들어 사용자가 봉투의 '카테고리' 값을 FAKE_VERTICAL로 설정하면 다음과 같은 메시지가 표시됩니다.

{
  "error": {
    "code": 400,
    "message": "Invalid value at 'entity.vertical' (TYPE_ENUM), \"FAKE_VERTICAL\"",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "entity.vertical",
            "description": "Invalid value at 'entity.vertical' (TYPE_ENUM), \"FAKE_VERTICAL\""
          }
        ]
      }
    ]
  }
}

코드 샘플

다음은 다양한 언어로 증분 업데이트 API를 사용하는 방법에 관한 몇 가지 샘플입니다. 이 샘플은 Google 인증 라이브러리를 사용하며 v1 인벤토리 스키마를 사용하는 피드를 가정합니다. 대체 솔루션은 서버 간 애플리케이션에서 OAuth 2.0 사용을 참조하세요.

항목 업데이트

Node.js

이 코드는 Node.js용 Google 인증 라이브러리를 사용합니다.

const {auth} = require('google-auth-library')
const request = require('request');
// The service account client secret file downloaded from the Google Cloud Console
const serviceAccountJson = require('./service-account.json')
// entity.json is a file that contains the entity data in json format
const entity = require('./entity.json')

const ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant'
const PROJECT_ID = 'your-project-id'

/**
 * Get the authorization token using a service account.
 */
async function getAuthToken() {
  let client = auth.fromJSON(serviceAccountJson)
  client.scopes = ['https://www.googleapis.com/auth/assistant']
  const tokens = await client.authorize()
  return tokens.access_token;
}

/**
 * Send an incremental update to update or add an entity
 */
async function updateEntity(entityId, entity) {
  const token = await getAuthToken()
  request.post({
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    url: `https://actions.googleapis.com/v2/apps/${PROJECT_ID}/entities/${encodeURIComponent(entityId)}:push`,
    body: {
      entity: {
        data: JSON.stringify(entity),
        vertical: 'FOODORDERING',
      }
    },
    json: true
  },
  (err, res, body) => {
    if (err) { return console.log(err); }
    console.log(`Response: ${JSON.stringify(res)}`)
  })
}

updateEntity(ENTITY_ID, entity)

Python

이 코드는 Python용 Google 인증 라이브러리를 사용합니다.

from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
import json
import urllib

PROJECT_ID = 'your-project-id'
ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant'
ENDPOINT = 'https://actions.googleapis.com/v2/apps/%s/entities/%s:push' % (
    PROJECT_ID, urllib.quote(ENTITY_ID, ''))

# service-account.json is the service account client secret file downloaded from the
# Google Cloud Console
credentials = service_account.Credentials.from_service_account_file(
    'service-account.json')

scoped_credentials = credentials.with_scopes(
    ['https://www.googleapis.com/auth/assistant'])

authed_session = AuthorizedSession(scoped_credentials)

# Retrieving the entity
update_file = open("entity.json")  #JSON file containing entity data in json format.
data = update_file.read()

# Populating the entity with wrapper
entity = {}
entity['data'] = data #entity JSON-LD serialized as string
entity['vertical'] = 'FOODORDERING'

request = {}
request['entity'] = entity

response = authed_session.post(ENDPOINT, json=request)

print(response.text) #if successful, will be '{}'

Java

이 코드는 Java용 Google 인증 라이브러리를 사용합니다.

private static final String PROJECT_ID = "your-project-id";
private static final String ENTITY_ID = "http://www.provider.com/somerestaurant";

/**
 * Get the authorization token using a service account.
 */
private static String getAuthToken() {
  InputStream serviceAccountFile =
      Example.class.getClassLoader().getResourceAsStream("service-account.json");
  ServiceAccountCredentials.Builder credentialsSimpleBuilder =
      ServiceAccountCredentials.fromStream(serviceAccountFile).toBuilder();
  credentialsSimpleBuilder.setScopes(ImmutableList.of("https://www.googleapis.com/auth/assistant"));
  AccessToken accessToken = credentialsSimpleBuilder.build().refreshAccessToken();
  return accessToken.getTokenValue();
}

/**
 * Send an incremental update to update or add an entity.
 * @param entityId The id of the entity to update.
 * @param entity the json of the entity to be updated.
 */
public void updateEntity(String entityId, JSONObject entity) {
  String authToken = getAuthToken();
  String endpoint = String.format(
      "https://actions.googleapis.com/v2/apps/%s/entities/%s:push",
      PROJECT_ID, URLEncoder.encode(entityId, "UTF-8"));
  JSONObject data = new JSONObject();
  data.put("data", entity.toString());
  data.put("vertical", "FOODORDERING");
  JSONObject jsonBody = new JSONObject();
  jsonBody.put("entity", data);
  // Execute POST request
  executePostRequest(endpoint, authToken, jsonBody);
}

항목 삭제

Node.js

이 코드는 Node.js용 Google 인증 라이브러리를 사용합니다.

const {auth} = require('google-auth-library')
const request = require('request');
// The service account client secret file downloaded from the Google Cloud Console
const serviceAccountJson = require('./service-account.json')
// entity.json is a file that contains the entity data in json format
const entity = require('./entity.json')

const ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant'
const PROJECT_ID = 'your-project-id'

/**
 * Get the authorization token using a service account.
 */
async function getAuthToken() {
  let client = auth.fromJSON(serviceAccountJson)
  client.scopes = ['https://www.googleapis.com/auth/assistant']
  const tokens = await client.authorize()
  return tokens.access_token;
}

/**
 * Send an incremental update to delete an entity
 */
async function deleteEntity(entityId) {
  const token = await getAuthToken()
  request.delete({
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    url: `https://actions.googleapis.com/v2/apps/${PROJECT_ID}/entities/${encodeURIComponent(entityId)}?entity.vertical=FOODORDERING`,
    body: {},
    json: true
  },
  (err, res, body) => {
    if (err) { return console.log(err); }
    console.log(`Response: ${JSON.stringify(res)}`)
  })
}

deleteEntity(ENTITY_ID)

Python

이 코드는 Python용 Google 인증 라이브러리를 사용합니다.

from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
import json
import urllib

# Service config
PROJECT_ID = 'your-project-id'
ENTITY_ID = 'restaurant/http://www.provider.com/somerestaurant'
DELETE_TIME = '2018-04-07T14:30:00-07:00'
ENDPOINT = 'https://actions.googleapis.com/v2/apps/%s/entities/%s?entity.vertical=FOODORDERING&delete_time=%s' % (
    PROJECT_ID, urllib.quote(ENTITY_ID, ''), urllib.quote(DELETE_TIME, ''))

# service-account.json is the service account client secret file downloaded from the
# Google Cloud Console
credentials = service_account.Credentials.from_service_account_file(
    'service-account.json')

scoped_credentials = credentials.with_scopes(
    ['https://www.googleapis.com/auth/assistant'])

authed_session = AuthorizedSession(scoped_credentials)
response = authed_session.delete(ENDPOINT)

print(response.text) #if successful, will be '{}'

Java

이 코드는 Java용 Google 인증 라이브러리를 사용합니다.

private static final String PROJECT_ID = "your-project-id";
private static final String ENTITY_ID = "restaurant/http://www.provider.com/somerestaurant";

/**
 * Get the authorization token using a service account.
 */
private static String getAuthToken() {
  InputStream serviceAccountFile = Example.class.getClassLoader().getResourceAsStream("service-account.json");
  ServiceAccountCredentials.Builder credentialsSimpleBuilder =
      ServiceAccountCredentials.fromStream(serviceAccountFile).toBuilder();
  credentialsSimpleBuilder.setScopes(ImmutableList.of("https://www.googleapis.com/auth/assistant"));
  AccessToken accessToken = credentialsSimpleBuilder.build().refreshAccessToken();
  return accessToken.getTokenValue();
}

/**
 * Send an incremental update to delete an entity.
 * @param entityId The id of the entity to delete.
 */
public void deleteEntity(String entityId) {
  String authToken = getAuthToken();
  String endpoint = String.format(
      "https://actions.googleapis.com/v2/apps/%s/entities/%s?entity.vertical=FOODORDERING",
      PROJECT_ID, URLEncoder.encode(entityId, "UTF-8"));
  // Execute DELETE request
  System.out.println(executeDeleteRequest(endpoint, authToken));
}

사용 사례

다음 사용 사례는 증분 업데이트, 전체 피드 업데이트, API 호출의 상위 수준 콘텐츠의 예입니다.

시나리오 최상위 항목 설명 및 효과
서비스 사용 중지 DisabledService

예기치 못한 이유로 서비스를 사용 중지해야 하는 경우

증분 업데이트: @typeDisabledService로 변경된 해당 Service 항목을 전송하되 다른 속성은 동일하게 유지합니다.

전체 피드: Google에서 다음에 가져오기 전에 @typeDisabledService로 설정되도록 전체 피드의 항목을 업데이트해야 합니다. 그러지 않으면 항목이 다시 사용 설정됩니다.

특정 상품의 재고가 없음 Menu 증분 업데이트: 지정된 MenuItemoffer.inventoryLevel가 0으로 설정된 캡슐화 Menu 항목을 전송하고 다른 모든 데이터는 변경하지 않고 전송합니다.
메뉴 항목 가격 변경 Menu 증분 업데이트: offer.price를 지정된 MenuItem의 업데이트된 가격으로 설정하고 다른 모든 데이터는 변경하지 않은 상태로 캡슐화된 Menu 항목을 전송합니다.

새 최상위 항목 추가

Menu, Restaurant, Service 유형의 항목에만 적용됩니다.

Menu, Restaurant, Service

예를 들어 식당에 새 메뉴를 추가해야 합니다.

증분 업데이트: hasMenu 필드가 있는 식당 항목과 함께 새 메뉴 항목을 보내면 그에 따라 업데이트됩니다.

최상위 항목을 영구적으로 삭제

Menu, Restaurant, Service 유형의 항목에만 적용됩니다.

Menu, Restaurant, Service

증분 업데이트: 명시적 삭제를 전송합니다.

전체 피드: Google에서 다음에 가져오기 전에 전체 피드에서 항목을 삭제해야 합니다. 그렇지 않으면 항목이 다시 추가됩니다.

특정 Service에 새 배송 지역 추가 Service 증분 피드: 문제의 Service 항목을 전체 피드 내에서 평소대로 모든 필드를 온전히 유지하고 새 전송 지역을 ServiceareaServed 내에 지정하여 전송합니다.
Service 배송 예상 도착 시간 업데이트 Service 증분 피드: hoursAvailable.deliveryHours가 이에 따라 업데이트된다는 점을 제외하고 피드와 동일하게 Service를 전송합니다.
Service의 배송 가격 업데이트 Service 증분 피드: offers.priceSpecification.price가 업데이트된 상태로 전체 Service를 전송합니다.
Service의 배달 또는 테이크아웃 시간 업데이트 Service 증분 피드: hoursAvailable가 이에 따라 업데이트된다는 점을 제외하고 피드와 동일하게 Service를 전송합니다.
Service (최소 주문 금액 변경) Service 증분 피드: Service.offers.priceSpecification.eligibleTransactionVolume가 업데이트된 상태로 전체 Service 전송
MenuItem 영구 삭제 Menu 증분 피드: 피드에서와 동일하게 Menu를 전송하되 이 MenuItemhasMenuItems 목록에서 삭제합니다.

일괄 작업 및 증분 업데이트의 처리 시간에 대한 SLO

일괄 또는 증분 업데이트를 통해 추가된 항목은 1~2일 이내에 처리됩니다. 일괄 처리를 통해 업데이트되거나 삭제된 항목은 2시간 이내에 처리되지만, 증분 업데이트를 통해 업데이트된 항목은 5분 이내에 처리됩니다. 비활성 항목은 7일 후에 삭제됩니다.

Google에 다음 중 하나를 전송할 수 있습니다.

  • 인벤토리를 최신 상태로 유지하기 위해 하루에 여러 개의 일괄 작업 수행
  • 일일 일괄 작업 1개와 증분 API로 인벤토리를 최신 상태로 유지합니다.