Incremental inventory updates

This section describes how you can send time-sensitive updates of your feeds to Google. The Incremental Updates API lets you update and delete entities in your feeds 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 Updates API should be a change that must go live in no more than one week. If your change does not need to be reflected immediately, you can use a batch update 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.

Endpoint

To notify Google of an update, make an HTTP POST request to the Incremental Updates API and include a payload of updates and additions. The inventory schema you use determines which endpoint to make your request to:

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

To remove an entity, make an HTTP DELETE request to the following endpoint that corresponds to which inventory schema you use:

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

In the above requests, replace the following:

  • PROJECT_ID: Google Cloud project ID associated with the project you created in Create and set up a project.
  • TYPE (v2 inventory schema only): The entity type (@type property) of the object in your data feed you want to update.
  • ENTITY_ID: ID of the entity included in the payload. 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

For example, you have a project with an ID of "delivery-provider-id" that uses the v2 inventory schema. You want to make changes to the restaurant with a restaurant entity type of "MenuSection" and an entity ID of "menuSection_122". The endpoint for updates to your data would be the following:

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

To remove this same entity, you would make this HTTP DELETE API call:

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

Sandbox requests

For sandbox requests, follow the guidance in Endpoint above, but make requests to /v2/sandbox/apps/ instead of to /v2/apps/. For instance, a sandbox delete request for v2 inventory schema is structured as follows:

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

Updates and additions

Your daily batch feeds should also contain any changes submitted through this API. Otherwise, your batch updates will overwrite your incremental changes.

Payload

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.

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:
{
  "entity": {
    "data":"ENTITY_DATA",
    "vertical":"FOODORDERING"
  },
  "update_time":"UPDATE_TIMESTAMP"
}

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.

Updating an entity

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/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"
  }
}

Example 2: 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/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"
  }
}

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.

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 parameter.

Example 1: 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 "https://www.provider.com/restaurant/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

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

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

Example 2: Removing sub-entities

To remove a sub-entity from within a top-level entity, you send the top-level entity with the sub-entity removed from the corresponding field. The following example assumes the feed uses the v1 inventory schema.

For example, to remove a service area, update the service with the service area removed from the areaServed list.

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 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 Updates 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 = '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

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 = '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

This code uses the Google auth library for Java.

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

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 = '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 = '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 = "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 Top-level entity Description and effects
Disabling a service DisabledService

You need to disable a service for an unforeseen reason.

Incremental updates: Send the Service entity in question with @type changed to DisabledService, but keep other properties the same.

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

Specific item is out-of-stock Menu Incremental updates: Send the encapsulating Menu entity with offer.inventoryLevel set to 0 for the given MenuItem, and all other data unchanged.
Menu item price change Menu Incremental updates: Send the encapsulating Menu entity with offer.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.

Incremental updates: Send the new menu entity, along with the restaurant entity with its field hasMenu is updated accordingly.

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 Service Incremental feeds: Send the Service entity in question with all its fields intact, like you normally would within the full feeds, with new delivery area specified within areaServed of the Service.
Update delivery estimated time of arrival in Service Service Incremental feeds: Send the Service the same as in the feeds, except its hoursAvailable.deliveryHours is updated accordingly.
Update delivery prices in Service Service Incremental feeds: Send full Service with offers.priceSpecification.price updated.
Update delivery or takeout hours in Service Service Incremental feeds: Send the Service the same as in the feeds, except its hoursAvailable is updated accordingly.
Service (change min order amount) Service Incremental feeds: Send full Service with Service.offers.priceSpecification.eligibleTransactionVolume updated
Delete MenuItem permanently Menu Incremental feeds: Send the Menu the same as in the feeds, but with this MenuItem removed from the hasMenuItems list.

SLO on processing time for batch jobs and incremental updates

An entity added through a batch or incremental update will be processed in 1-2 days. An entity updated or deleted through a batch will be processed in 2 hours 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.