v2 incremental inventory updates

This section describes how you can send time-sensitive updates of your inventory entities to Google. The Incremental Update API lets you push updates and delete entities in your Sandbox or Production inventory in nearly real time.

This functionality is primarily intended for updates that you can't foresee, such as emergency closures. As a rule, any change submitted through the Incremental Update API should be a change that must go live in no more than an hour. If your change does not need to be reflected immediately, you can use the batch ingestion instead. Incremental updates are processed in no more than five minutes.

Prerequisites

The following items are required before you implement incremental updates:

  1. A service account is created with the editor role to your Actions project. For more details, see Create and set up a project.
  2. Production or sandbox data feeds are hosted and ingested. For more details, see Batch ingestion.
  3. (Optional, but recommended) Install the Google Client library in the language of your choice to facilitate using OAuth 2.0 when calling the API. The code samples included below use these libraries. Otherwise, you'll need to handle token exchanges manually as described in Using OAuth 2.0 to Access Google APIs.

Endpoints

In the below requests, replace the following:

  • PROJECT_ID: Google Cloud project ID associated with the project you created in Create and set up a project.
  • TYPE: The entity type (@type property) of the object in your data feed you want to update.
  • ENTITY_ID (delete endpoint only): ID of the entity to be deleted. Make sure to URL encode your entity ID.
  • DELETE_TIME (delete endpoint only): Optional field to denote the time the entity was deleted on your systems (default is when the request is received). Time value must not be in the future. When sending an entity through an incremental call, entity versioning also uses the delete_time field in the case of a delete call. Format this value as yyyy-mm-ddTHH:mm:ssZ

Update endpoint

To modify an entity, make an HTTP POST request to the following endpoint and include a payload of updates and additions. You can make updates up to 1,000 entities in a single API call.

https://actions.googleapis.com/v2/apps/PROJECT_ID/entities:batchPush

For example, if you want to update entities in a project with an ID "delivery-provider-id" the endpoint would be the following:

https://actions.googleapis.com/v2/apps/delivery-provider-id/entities:batchpush

Delete endpoint

To delete an entity in your inventory, make an HTTP DELETE request to the following endpoint.

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

For example, to delete a "MenuSection" entity with ID "menuSection_122" from your "delivery-provider-id" project, you would make an HTTP DELETE API call to:

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

Sandbox environment

To use the Incremental Update API in your sandbox inventory, follow the guidance in the Endpoints above, but make requests to /v2/sandbox/apps/ instead of to /v2/apps/.

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

Updating entities

Each POST request must include the request parameters along with the JSON payload containing the structured data of any entity type listed in the inventory schema.

Update payload

The JSON should appear the same as it would in the batch feed, with the following differences:

  • The payload body should not exceed 5 MB in size. Similarly to batch feeds, we suggest you strip whitespaces in the interest of fitting more data.
  • The envelope is as follows:
{
  "requests": [
    {
      "entity": {
        "data":"ENTITY_DATA",
        "name": "apps/project_id>/entities/type/entity_id"
      },
      "update_time":"UPDATE_TIMESTAMP"
    },
  ],
  "vertical": "FOODORDERING"
}

In the above payload, replace the following:

  • ENTITY_DATA: Entity in JSON format serialized as a string. The JSON-LD entity must be passed as a string in the data field.
  • UPDATE_TIMESTAMP (optional): Timestamp when entity was updated in your systems. Time value must not be in the future. Default timestamp is when Google receives the request. When sending an entity through an incremental request, the entity versioning also uses the update_time field in the case of an add/update request.

Examples

Example 1: Updating a restaurant

Suppose you urgently need to update the phone number of a restaurant. Your update contains the JSON for the entire restaurant.

Consider a batch feed that looks like the following:

{
  "@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
}

Then your incremental update by HTTP POST would be as follows:

POST v2/sandbox/apps/provider-project/entities:batchPush
Host: actions.googleapis.com
Content-Type: application/ld+json
{
  "requests": [
    {
      "entity": {
        "name": "apps/provider-project/entities/restaurant/restaurant12345",
        "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"
}

Example 2: Updating multiple restaurants

To update two restaurant entities in a single API call, the HTTP POST request would be as follows:

POST v2/sandbox/apps/provider-project/entities:batchPush
Host: actions.googleapis.com
Content-Type: application/ld+json
{
  "requests": [
    {
      "entity": {
        "name": "apps/provider-project/entities/restaurant/restaurant12345",
        "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
        }
      }
    },
    {
      "entity": {
        "name": "apps/provider-project/entities/restaurant/restaurant123",
        "data": {
          "@type": "Restaurant",
          "@id": "restaurant123",
          "name": "Some Other Restaurant",
          "url": "https://www.provider.com/somerestaurant",
          "telephone": "+16501231235",
          "streetAddress": "385 Spear St",
          "addressLocality": "San Mateo",
          "addressRegion": "CA",
          "postalCode": "94115",
          "addressCountry": "US"
        }
      }
    }
  ]
  "vertical": "FOODORDERING"
}

Example 3: Updating a menu item price

Suppose you need to change the price of a menu item. As in Example 1, your update must contain the JSON for the entire top-level entity (the menu), and the feed uses the v1 inventory schema.

Consider a batch feed that looks like the following:

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

Then your incremental update via POST would be as follows:

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

Adding an entity

To add entities, avoid using inventory updates. Instead, use the batch feeds process as described for the v2 inventory schema.

Removing an entity

To remove top-level entities, you use a slightly modified endpoint, and use HTTP DELETE instead of HTTP POST in the request.

Deleting a top-level entity

Consider a situation where you want to delete a restaurant in a feed. You must also delete its services and menus.

A sample endpoint for a menu entity with ID "provider/restaurant/menu/nr":

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

A sample endpoint for a restaurant entity with ID "https://www.provider.com/restaurant/nr":

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

A sample endpoint for a service entity with ID "https://www.provider.com/restaurant/service/nr":

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

Removing sub-entities

Don't use HTTP DELETE to remove a sub-entity within a top-level entity, such as a menu item within a menu. Instead, treat the removal of sub-entities as an update to a top-level entity in which the sub-entity is removed from the relevant list or reverseReference.

API response codes

A successful call does not mean that the feed is valid or correct, only that the API call was made. Successful calls receive an HTTP response code 200, along with an empty response body:

{}

For failures, the HTTP response code will not be 200, and the response body indicates what went wrong.

For example, if the user has set the "vertical" value in the envelope to FAKE_VERTICAL, you would receive the message below:

{
  "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\""
          }
        ]
      }
    ]
  }
}

Code sample

Below are some samples of how to use the Incremental Update API in various languages. These samples use the Google Auth Libraries, and assume a feed using the v1 inventory schema. For alternative solutions, refer to Using OAuth 2.0 for Server to Server Applications.

Updating entities

Node.js

This code uses the Google auth library for Node.js.

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 = 'your/entity/id'
const PROJECT_ID = 'type/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(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:batchPush`,
    body: {
      requests: [
        {
          entity: {
            data: JSON.stringify(entity)
            name: `apps/${PROJECT_ID}/entities/${ENTITY_ID}`
          }
        }
      ],
      vertical: 'FOODORDERING'
    },
    json: true
  },
  (err, res, body) => {
    if (err) { return console.log(err); }
    console.log(`Response: ${JSON.stringify(res)}`)
  })
}

updateEntity(entity)

Python

This code uses the Google auth library for Python.

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

PROJECT_ID = 'your-project-id'
ENTITY_ID = 'type/your/entity/id'

ENDPOINT = 'https://actions.googleapis.com/v2/apps/%s/entities:batchPush' % (
    PROJECT_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()

entity = {}
entity['data'] = data #entity JSON-LD serialized as string
entity['name'] = 'apps/%s/entities/%s' % (PROJECT_ID, urllib.quote(ENTITY_ID, '') )

# Populating the request
request = {}
request['entity'] = entity
requestArray = [request]

# Populating the payload
payload = {}
payload['requests'] = requestArray
payload['vertical'] = 'FOODORDERING'

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

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

Java

This code uses the Google auth library for Java.

private static final String PROJECT_ID = "your-project-id";
private static final String ENTITY_ID = "type/your-entity-id";

/**
 * 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 data) {
  String authToken = getAuthToken();
  String endpoint = String.format("https://actions.googleapis.com/v2/apps/%s/entities/:batchPush", PROJECT_ID);

  JSONObject entity = new JSONObject();
  entity.put("data", data.toString());
  entity.put("name", String.format("apps/%s/entities/%s", PROJECT_ID, URLEncoder.encode(ENTITY_ID, "UTF-8")));

  JSONObject request = new JSONObject();
  request.put("entity", entity);

  JSONArray requestArray = new JSONArray();
  requestArray.put(request);

  JSONObject payload = new JSONObject();
  payload.put("requests", requestArray);
  payload.put("vertical", FOODORDERING);

  // Execute POST request
  executePostRequest(endpoint, authToken, payload);
}

Removing entities

Node.js

This code uses the Google auth library for Node.js.

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

This code uses the Google auth library for Python.

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

This code uses the Google auth library for Java.

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));
}

Use cases

The following use cases are examples of incremental updates, full feed updates, and the content at a high level in the API call:

Scenario Entity to update Description and effects
Disabling a service Service

You need to disable a service for an unforeseen reason.

Incremental updates: Update the Service entity in question by setting its isDisabled property to true, but keep other properties the same.

Full feeds: Make sure to update the entity from the full feeds to have isDisabled set to true prior to the next fetch by Google, otherwise the entity will get re-enabled.

Specific item is out-of-stock MenuItemOffer Incremental updates: Send the encapsulating MenuItemOffer entity with inventoryLevel set to 0 for the given MenuItem, and all other data unchanged.
Menu item price change MenuItemOffer Incremental updates: Send the encapsulating MenuItemOffer entity with price set to the updated price for the given MenuItem, and all other data unchanged.

Add new top-level entity

Only applicable for entity of types Menu, Restaurant, and Service.

Menu, Restaurant, Service

For instance, you need to add a new menu to a restaurant.

Full feeds: Add the entity in your data feeds and wait for batch ingestion.

Delete top-level entity permanently

Only applicable for entity of types Menu, Restaurant, and Service.

Menu, Restaurant, Service

Incremental updates: Send an explicit delete.

Full feeds: Make sure to remove the entity from the full feeds prior to the next fetch by Google, otherwise the entity will get re-added.

Add a new delivery area in a specific Service ServiceArea Incremental feeds: Send the ServiceArea entity in question with all its fields intact, like you normally would within the full feeds, with new delivery area specified within polygon, geoRadius, or postalCode.
Update delivery estimated time of arrival in Service ServiceHours Incremental feeds: Send the ServiceHours the same as in the feeds, except its leadTimeMin is updated accordingly.
Update delivery prices in Service Fee Incremental feeds: Send full delivery Fee with price updated.
Update delivery or takeout hours in Service ServiceHours Incremental feeds: Send the ServiceHours the same as in the feeds, except its opens and closes properties are updated accordingly.
Service (change min order amount) Fee Incremental feeds: Send full Fee with minPrice updated
Delete a MenuItem permanently Menu Incremental feeds: Send the MenuItem the same as in the feeds, but with parentMenuSectionId empty.

SLO on processing time for batch jobs and incremental updates

An entity updated or deleted through a batch will be processed within 2 hours in best effort mode whereas an entity updated through an incremental update will be processed in 5 minutes. A stale entity is deleted in 7 days.

You can either send Google:

  • Multiple batch jobs per day to keep your inventory up to date, OR
  • One batch job per day and Incremental APIs to keep your inventory up to date.