Atualizações incrementais de inventário v1

Nesta seção, descrevemos como enviar atualizações urgentes dos seus feeds para o Google. A API Incremental Updates permite atualizar e excluir entidades nos feeds quase em tempo real.

Essa funcionalidade é destinada principalmente a atualizações que não podem ser previstas, como interdições de emergência. Como regra geral, qualquer alteração enviada pela API Incremental Updates precisa ser publicada em no máximo uma semana. Se a mudança não precisar ser refletida imediatamente, use uma atualização em lote. As atualizações incrementais são processadas em no máximo cinco minutos.

Configuração

Para implementar atualizações incrementais, faça o seguinte:

  1. Siga as etapas descritas em Criar e configurar um projeto para criar um projeto.
  2. Siga as etapas descritas em Configurar uma conta de serviço para criar uma conta de serviço. É preciso ser "Proprietário" do projeto para adicionar um papel de "Editor" à conta de serviço
  3. (Opcional, mas recomendado) Instale a biblioteca de cliente do Google na linguagem que você quiser para facilitar o uso do OAuth 2.0 ao chamar a API. Os exemplos de código incluídos abaixo usam essas bibliotecas. Caso contrário, você precisará processar as trocas de tokens manualmente, conforme descrito em Como usar o OAuth 2.0 para acessar as APIs do Google.

Endpoint

Para notificar o Google sobre uma atualização, faça uma solicitação HTTP POST para a API Incremental Updates e inclua um payload de atualizações e adições. O esquema de inventário usado determina para qual endpoint sua solicitação será feita:

Inventário v2

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

Inventário v1

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

Para remover uma entidade, faça uma solicitação HTTP DELETE para o seguinte endpoint que corresponde ao esquema de inventário usado:

Inventário v2

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

Inventário v1

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

Nas solicitações acima, substitua o seguinte:

  • PROJECT_ID: ID do projeto do Google Cloud associado ao projeto criado em Criar e configurar um projeto.
  • TYPE (somente esquema de inventário v2): o tipo de entidade (propriedade @type) do objeto no feed de dados que você quer atualizar.
  • ENTITY_ID: ID da entidade incluída no payload. Codifique o URL do ID da entidade.
  • DELETE_TIME (excluir endpoint apenas): campo opcional para indicar o horário em que a entidade foi excluída dos seus sistemas. O padrão é quando a solicitação é recebida. O valor de tempo não pode ser no futuro. Ao enviar uma entidade por uma chamada incremental, o controle de versões da entidade também usa o campo delete_time no caso de uma chamada de exclusão. Formate esse valor como yyyy-mm-ddTHH:mm:ssZ

Por exemplo, você tem um projeto com um ID "delivery-provider-id" que usa o esquema de inventário v2. Você quer fazer mudanças no restaurante com um tipo de entidade de restaurante de "MenuSection" e um ID de entidade "menuSection_122". O endpoint para atualizações dos dados vai ser o seguinte:

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

Para remover essa mesma entidade, faça esta chamada de API HTTP DELETE:

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

Solicitações de sandbox

No caso de solicitações de sandbox, siga as orientações no Endpoint acima, mas faça solicitações para /v2/sandbox/apps/ em vez de /v2/apps/. Por exemplo, uma solicitação de exclusão de sandbox para o esquema de inventário v2 é estruturada da seguinte maneira:

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

Atualizações e adições

Os feeds diários em lote também precisam conter as mudanças enviadas por essa API. Caso contrário, as atualizações em lote vão substituir as mudanças incrementais.

Payload

Cada solicitação POST precisa incluir os parâmetros de solicitação junto com o payload JSON que contém os dados estruturados de qualquer tipo de entidade listado no esquema do inventário.

O JSON precisa aparecer da mesma forma que no feed em lote, com as seguintes diferenças:

  • O corpo do payload não pode ter mais de 5 MB. Assim como nos feeds em lote, sugerimos que você remova espaços em branco para ajustar mais dados.
  • O envelope é o seguinte:
{
  "entity": {
    "data":"ENTITY_DATA",
    "vertical":"FOODORDERING"
  },
  "update_time":"UPDATE_TIMESTAMP"
}

No payload acima, substitua o seguinte:

  • ENTITY_DATA: entidade no formato JSON serializada como uma string. A entidade JSON-LD precisa ser transmitida como uma string no campo data.
  • UPDATE_TIMESTAMP (opcional): carimbo de data/hora quando a entidade foi atualizada nos seus sistemas. O valor de tempo não pode ser no futuro. O carimbo de data/hora padrão é quando o Google recebe a solicitação. Ao enviar uma entidade por uma solicitação incremental, o controle de versões da entidade também usa o campo update_time no caso de uma solicitação de adição/atualização.

Atualizar uma entidade

Exemplo 1: atualizar um restaurante

Suponha que você precise atualizar com urgência o número de telefone de um restaurante. A atualização contém o JSON de todo o restaurante.

Considere um feed em lote com a seguinte aparência:

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

Então, a atualização incremental por HTTP POST seria a seguinte:

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

Exemplo 2: atualizar o preço de um item do cardápio

Suponha que você precise mudar o preço de um item do menu. Como no Exemplo 1, sua atualização precisa conter o JSON para toda a entidade de nível superior (o menu) e o feed usa o esquema de inventário v1.

Considere um feed em lote com a seguinte aparência:

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

Então, a atualização incremental via POST seria a seguinte:

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

Como adicionar uma entidade

Para adicionar entidades, evite usar atualizações de inventário. Em vez disso, use o processo de feeds em lote conforme descrito para o esquema de inventário v2.

Como remover uma entidade

Para remover entidades de nível superior, use um endpoint levemente modificado e utilize HTTP DELETE em vez de HTTP POST na solicitação.

Não use HTTP DELETE para remover uma subentidade em uma entidade de nível superior, como um item em um menu. Em vez disso, trate a remoção de subentidades como uma atualização de uma entidade de nível superior em que a subentidade é removida da lista ou parâmetro relevante.

Exemplo 1: exclusão de uma entidade de nível superior

Imagine uma situação em que você quer excluir um restaurante em um feed que usa o esquema de inventário v1. Você também precisa excluir os serviços e menus dele.

Um endpoint de exemplo para uma entidade de menu com o ID "https://www.provider.com/restaurante/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

Um endpoint de exemplo para uma entidade de restaurante com o ID "https://www.provider.com/restaurante/nr":

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

Um endpoint de exemplo para uma entidade de serviço com o ID "https://www.provider.com/restaurante/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
}

Exemplo 2: remoção de subentidades

Para remover uma subentidade de uma entidade de nível superior, envie a entidade de nível superior com a subentidade removida do campo correspondente. O exemplo a seguir supõe que o feed use o esquema de inventário v1.

Por exemplo, para remover uma área de cobertura, atualize o serviço com ela removida da lista 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"
  }
}

Códigos de resposta da API

Uma chamada bem-sucedida não significa que o feed seja válido ou correto, apenas que a chamada de API foi feita. As chamadas bem-sucedidas recebem um código de resposta HTTP 200, com um corpo de resposta vazio:

{}

Para falhas, o código de resposta HTTP não será 200, e o corpo da resposta indica o que deu errado.

Por exemplo, se o usuário tiver definido o valor "vertical" no envelope como FAKE_VERTICAL, você receberá a mensagem abaixo:

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

Exemplo de código

Abaixo estão alguns exemplos de como usar a API Incremental Updates em várias linguagens. Esses exemplos usam as bibliotecas do Google Auth e consideram um feed que usa o esquema de inventário v1. Para soluções alternativas, consulte Usar o OAuth 2.0 para aplicativos de servidor para servidor.

Como atualizar entidades

Node.js

Esse código usa a biblioteca de autenticação do Google para 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 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

Esse código usa a biblioteca de autenticação do Google para 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 = '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

Esse código usa a biblioteca de autenticação do Google para 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);
}

Como remover entidades

Node.js

Esse código usa a biblioteca de autenticação do Google para 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

Esse código usa a biblioteca de autenticação do Google para 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

Esse código usa a biblioteca de autenticação do Google para 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));
}

Casos de uso

Os casos de uso a seguir são exemplos de atualizações incrementais, atualizações completas de feed e o conteúdo em um alto nível na chamada de API:

Cenário Entidade de nível superior Descrição e efeitos
Desativar um serviço DisabledService

for necessário desativar um serviço por um motivo inesperado.

Atualizações incrementais: envie a entidade Service em questão com @type alterado para DisabledService, mas mantenha as outras propriedades.

Feeds completos: atualize a entidade nos feeds completos para que o @type seja definido como DisabledService antes da próxima busca do Google. Caso contrário, a entidade será reativada.

Item específico esgotado Menu Atualizações incrementais: envie a entidade Menu de encapsulamento com offer.inventoryLevel definido como 0 para o MenuItem especificado e todos os outros dados inalterados.
Mudança no preço do item do cardápio Menu Atualizações incrementais: envie a entidade Menu de encapsulamento com offer.price definido como o preço atualizado para o MenuItem especificado e todos os outros dados inalterados.

Adicionar nova entidade de nível superior

Aplicável apenas às entidades dos tipos Menu, Restaurant e Service.

Menu, Restaurant, Service

Por exemplo, você precisa adicionar um novo cardápio a um restaurante.

Atualizações incrementais: envie a nova entidade do cardápio, a entidade do restaurante e o campo hasMenu é atualizado de acordo.

Excluir permanentemente a entidade de nível superior

Aplicável apenas às entidades dos tipos Menu, Restaurant e Service.

Menu, Restaurant, Service

Atualizações incrementais:envie uma exclusão explícita.

Feeds completos:remova a entidade dos feeds completos antes da próxima busca do Google. Caso contrário, ela será adicionada novamente.

Adicionar uma nova área de entrega em um Service específico Service Feeds incrementais: envie a entidade Service em questão com todos os campos intactos, como faria normalmente nos feeds completos, com uma nova área de entrega especificada em areaServed de Service.
Atualizar o horário previsto de chegada para Service Service Feeds incrementais: envie Service da mesma forma que nos feeds, mas o hoursAvailable.deliveryHours é atualizado.
Atualize os preços de entrega para Service Service Feeds incrementais: envie Service completo com offers.priceSpecification.price atualizado.
Atualize os horários de entrega ou retirada em Service Service Feeds incrementais: envie Service da mesma forma que nos feeds, mas o hoursAvailable é atualizado.
Service (mudar valor mínimo do pedido) Service Feeds incrementais: envie Service completo com Service.offers.priceSpecification.eligibleTransactionVolume atualizado.
Excluir MenuItem permanentemente Menu Feeds incrementais:envie Menu da mesma forma que nos feeds, mas com esse MenuItem removido da lista de hasMenuItems.

SLO no tempo de processamento para jobs em lote e atualizações incrementais

Uma entidade adicionada por uma atualização em lote ou incremental será processada em um ou dois dias. Uma entidade atualizada ou excluída por meio de um lote será processada em duas horas. Já uma entidade atualizada por meio de uma atualização incremental será processada em cinco minutos. Uma entidade desatualizada é excluída em sete dias.

Você pode enviar ao Google:

  • Fazer várias tarefas em lote por dia para manter o inventário atualizado OU
  • Um job em lote por dia e APIs incrementais para manter seu inventário atualizado.