运作方式

简介

零触摸注册 API 可帮助设备转销商自动进行集成。贵组织的销售工具可用于构建零触摸注册,让您的用户和客户的工作效率更高。使用 API 帮助您的用户:

  • 将购买的设备分配给客户的零触摸注册帐号。
  • 为您的客户创建零触摸注册帐号。
  • 将贵组织的电话和订单元数据附加到设备。
  • 针对分配给您的客户的设备创建报告。

本文档介绍了此 API 并介绍了相关模式。如果您想自行探索该 API,请尝试阅读 Java.NETPython 快速入门。

API 概念

客户和设备是您在 API 中使用的核心资源。如需创建客户,请调用 create。您可以使用声明 API 方法创建设备(见下文)。您的组织也可以使用零触摸注册门户创建客户和设备。

设备和客户资源关系

客户
贵组织向其销售设备的公司。客户有 nameID。如要申领或查找客户的设备,请询问客户。如需了解详情,请参阅 Customer
设备
贵组织向客户销售的支持零触摸注册的 Android 或 ChromeOS 设备。设备具有硬件 ID、元数据和客户声明。设备是 API 的核心,因此您在几乎所有方法中都会用到设备。如需了解详情,请参阅 Device
DeviceIdentifier
封装用于识别制造设备的硬件 ID,例如 IMEI 或 MEID。使用 DeviceIdentifier 定位到您要查找、更新或声明的设备。如需了解详情,请参阅标识符
DeviceMetadata
存储设备的元数据键值对。使用 DeviceMetadata 存储组织的元数据。如需了解详情,请参阅设备元数据

如需列出您的应用可以使用的所有 API 方法和资源,请参阅 API 参考文档

创建客户

对于 Android 设备,转销商负责代表客户创建客户帐号。客户将使用此帐号访问零触摸门户,为其设备配置配置设置。对于已经拥有 Google Workspace 帐号用于配置配置设置的 ChromeOS 设备,则不需要执行此操作。

您可以调用 create API 方法来创建用于零触摸注册的客户帐号。由于客户会在其零触摸注册门户中看到公司名称,因此应用用户应确认其正确无误。创建客户后,您便无法修改客户的名称。

您需要至少添加一个与 Google 帐号相关联的公司电子邮件地址,才能成为所有者。您无法将个人 Gmail 帐号与 API 搭配使用。如果客户在关联账号方面需要帮助,请发送关联 Google 账号中的说明。

在您通过调用 API 创建客户后,客户会管理其员工的门户访问权限,而您无法使用 API 修改客户的用户。以下代码段展示了如何创建客户:

Java

// Provide the customer data as a Company type.
// The API requires a name and owners.
Company customer = new Company();
customer.setCompanyName("XYZ Corp");
customer.setOwnerEmails(Arrays.asList("liz@example.com", "darcy@example.com"));
customer.setAdminEmails(Collections.singletonList("jane@example.com"));

// Use our reseller ID for the parent resource name.
String parentResource = String.format("partners/%d", PARTNER_ID);

// Call the API to create the customer using the values in the company object.
CreateCustomerRequest body = new CreateCustomerRequest();
body.setCustomer(customer);
Company response = service.partners().customers().create(parentResource, body).execute();

.NET

// Provide the customer data as a Company type.
// The API requires a name and owners.
var customer = new Company
{
    CompanyName = "XYZ Corp",
    OwnerEmails = new String[] { "liz@example.com", "darcy@example.com" },
    AdminEmails = new String[] { "jane@example.com" }
};

// Use our reseller ID for the parent resource name.
var parentResource = String.Format("partners/{0}", PartnerId);

// Call the API to create the customer using the values in the company object.
var body = new CreateCustomerRequest
{
    Customer = customer
};
var request = service.Partners.Customers.Create(body, parentResource);
var response = request.Execute();

Python

# Provide the customer data as a Company type. The API requires
# a name and at least one owner.
company = {'companyName':'XYZ Corp', \
  'ownerEmails':['liz@example.com', 'darcy@example.com'], \
  'adminEmails':['jane@example.com']}

# Use our reseller ID for the parent resource name.
parent_resource = 'partners/{0}'.format(PARTNER_ID)

# Call the API to create the customer using the values in the company object.
response = service.partners().customers().create(parent=parent_resource,
    body={'customer':company}).execute()

如需详细了解客户员工的所有者和管理员角色,请参阅门户用户

为客户领取设备

客户购买设备后,需要在帐号中为这些设备配置配置设置。认领设备会将设备添加到零触摸注册,并使客户能够配置配置设置。

设备的配置记录包含用于零触摸注册的部分。您可以通过为客户声明该记录的零触摸注册部分来分配设备。使用客户作为参数,调用 partners.devices.claimpartners.devices.claimAsync 方法。始终提供 SECTION_TYPE_ZERO_TOUCH 作为 sectionType 的值。

您需要先取消声明(见下文)某个客户的设备,然后才能为其他客户声明同一设备。在创建新设备时,声明方法会validate DeviceIdentifier 字段,包括 IMEI 或 MEID,或序列号、制造商名称和型号,以及 ChromeOS 设备的经认证设备 ID。

以下代码段展示了如何认领设备:

Java

// Identify the device to claim.
DeviceIdentifier identifier = new DeviceIdentifier();
// The manufacturer value is optional but recommended for cellular devices
identifier.setManufacturer("Google");
identifier.setImei("098765432109875");

// Create the body to connect the customer with the device.
ClaimDeviceRequest body = new ClaimDeviceRequest();
body.setDeviceIdentifier(identifier);
body.setCustomerId(customerId);
body.setSectionType("SECTION_TYPE_ZERO_TOUCH");

// Claim the device.
ClaimDeviceResponse response = service.partners().devices().claim(PARTNER_ID, body).execute();

.NET

// Identify the device to claim.
var deviceIdentifier = new DeviceIdentifier
{
    // The manufacturer value is optional but recommended for cellular devices
    Manufacturer = "Google",
    Imei = "098765432109875"
};

// Create the body to connect the customer with the device.
ClaimDeviceRequest body = new ClaimDeviceRequest
{
    DeviceIdentifier = deviceIdentifier,
    CustomerId = CustomerId,
    SectionType = "SECTION_TYPE_ZERO_TOUCH"
};

// Claim the device.
var response = service.Partners.Devices.Claim(body, PartnerId).Execute();

Python

# Identify the device to claim.
# The manufacturer value is optional but recommended for cellular devices
device_identifier = {'manufacturer':'Google', 'imei':'098765432109875'}

# Create the body to connect the customer with the device.
request_body = {'deviceIdentifier':device_identifier, \
    'customerId':customer_id, \
    'sectionType':'SECTION_TYPE_ZERO_TOUCH'}

# Claim the device.
response = service.partners().devices().claim(partnerId=PARTNER_ID,
    body=request_body).execute()

正在取消声明设备

贵组织可以取消客户对设备的所有权。取消声明设备会将其从零触摸注册中移除。转销商可能会取消对要迁移到其他帐号、被退回或误认领的设备的所有权。调用 partners.devices.unclaimpartners.devices.unclaimAsync 方法,向客户取消声明对设备的所有权。

供应商

您可以使用供应商来代表经销商网络中的转销商合作伙伴、全球转销商网络中的本地运营商,或者代表您销售设备的任何组织。供应商可帮助您分离用户、客户和设备:

  • 您创建的供应商无法查看您的零触摸注册帐号或彼此的帐号。
  • 您可以查看供应商的客户和设备,也可以取消注册供应商的设备。但是,您无法将设备分配给供应商的客户。

请使用门户为组织创建供应商 - 您无法使用 API。您的帐号角色必须是 Owner 才能创建新供应商。如果贵组织有供应商,您可以调用 partners.vendors.list 列出供应商,调用 partners.vendors.customers.list 获取供应商的客户。以下示例使用这两种方法输出一份报告,其中显示供应商客户的服务条款状态:

Java

// First, get the organization's vendors.
String parentResource = String.format("partners/%d", PARTNER_ID);
ListVendorsResponse results = service.partners().vendors().list(parentResource).execute();
if (results.getVendors() == null) {
  return;
}

// For each vendor, report the company name and a maximum 5 customers.
for (Company vendor: results.getVendors()) {
  System.out.format("\n%s customers\n", vendor.getCompanyName());
  System.out.println("---");
  // Use the vendor's API resource name as the parent resource.
  AndroidProvisioningPartner.Partners.Vendors.Customers.List customerRequest =
      service.partners().vendors().customers().list(vendor.getName());
  customerRequest.setPageSize(5);
  ListVendorCustomersResponse customerResponse = customerRequest.execute();

  List<Company> customers = customerResponse.getCustomers();
  if (customers == null) {
    System.out.println("No customers");
    break;
  } else {
    for (Company customer: customers) {
      System.out.format("%s: %s\n",
          customer.getCompanyName(),
          customer.getTermsStatus());
    }
  }
}

.NET

// First, get the organization's vendors.
var parentResource = String.Format("partners/{0}", PartnerId);
var results = service.Partners.Vendors.List(parentResource).Execute();
if (results.Vendors == null)
{
    return;
}

// For each vendor, report the company name and a maximum 5 customers.
foreach (Company vendor in results.Vendors)
{
    Console.WriteLine("\n{0} customers", vendor);
    Console.WriteLine("---");
    // Use the vendor's API resource name as the parent resource.
    PartnersResource.VendorsResource.CustomersResource.ListRequest customerRequest =
        service.Partners.Vendors.Customers.List(vendor.Name);
    customerRequest.PageSize = 5;
    var customerResponse = customerRequest.Execute();

    IList<Company> customers = customerResponse.Customers;
    if (customers == null)
    {
        Console.WriteLine("No customers");
        break;
    }
    else
    {
        foreach (Company customer in customers)
        {
            Console.WriteLine("{0}: {1}", customer.Name, customer.TermsStatus);
        }
    }
}

Python

# First, get the organization's vendors.
parent_resource = 'partners/{0}'.format(PARTNER_ID)
vendor_response = service.partners().vendors().list(
    parent=parent_resource).execute()
if 'vendors' not in vendor_response:
  return

# For each vendor, report the company name and a maximum 5 customers.
for vendor in vendor_response['vendors']:
  print '\n{0} customers'.format(vendor['companyName'])
  print '---'
  # Use the vendor's API resource name as the parent resource.
  customer_response = service.partners().vendors().customers().list(
      parent=vendor['name'], pageSize=5).execute()
  if 'customers' not in customer_response:
    print 'No customers'
    break
  for customer in customer_response['customers']:
    print '  {0}: {1}'.format(customer['name'], customer['termsStatus'])

如果您拥有一系列设备,则可能需要知道哪个转销商或供应商声明了该设备。如需获取数字形式的转销商 ID,请检查设备声明记录中 resellerId 字段的值。

贵组织可以取消对供应商已声明的设备的所有权。对于修改设备的其他 API 调用,您应该先检查您的组织是否声明了设备的所有权,然后再调用相应 API 方法。以下示例展示了如何执行此操作:

Java

// Get the devices claimed for two customers: one of our organization's
// customers and one of our vendor's customers.
FindDevicesByOwnerRequest body = new FindDevicesByOwnerRequest();
body.setSectionType("SECTION_TYPE_ZERO_TOUCH");
body.setCustomerId(Arrays.asList(resellerCustomerId, vendorCustomerId));
body.setLimit(MAX_PAGE_SIZE);
FindDevicesByOwnerResponse response =
    service.partners().devices().findByOwner(PARTNER_ID, body).execute();
if (response.getDevices() == null) {
  return;
}

for (Device device: response.getDevices()) {
  // Confirm the device was claimed by our reseller and not a vendor before
  // updating metadata in another method.
  for (DeviceClaim claim: device.getClaims()) {
    if (claim.getResellerId() == PARTNER_ID) {
      updateDeviceMetadata(device.getDeviceId());
      break;
    }
  }
}

.NET

// Get the devices claimed for two customers: one of our organization's
// customers and one of our vendor's customers.
FindDevicesByOwnerRequest body = new FindDevicesByOwnerRequest
{
    Limit = MaxPageSize,
    SectionType = "SECTION_TYPE_ZERO_TOUCH",
    CustomerId = new List<long?>
    {
        resellerCustomerId,
        vendorCustomerId
    }
};
var response = service.Partners.Devices.FindByOwner(body, PartnerId).Execute();
if (response.Devices == null)
{
    return;
}

foreach (Device device in response.Devices)
{
    // Confirm the device was claimed by our reseller and not a vendor before
    // updating metadata in another method.
    foreach (DeviceClaim claim in device.Claims)
    {
        if (claim.ResellerId == PartnerId)
        {
            UpdateDeviceMetadata(device.DeviceId);
            break;
        }
    }
}

Python

# Get the devices claimed for two customers: one of our organization's
# customers and one of our vendor's customers.
request_body = {'limit':MAX_PAGE_SIZE, \
  'pageToken':None, \
  'customerId':[reseller_customer_id, vendor_customer_id], \
  'sectionType':'SECTION_TYPE_ZERO_TOUCH'}
response = service.partners().devices().findByOwner(partnerId=PARTNER_ID,
    body=request_body).execute()

for device in response['devices']:
  # Confirm the device was claimed by our reseller and not a vendor before
  # updating metadata in another method.
  for claim in device['claims']:
    if claim['resellerId'] == PARTNER_ID:
      update_device_metadata(device['deviceId'])
      break

长时间运行的批量操作

该 API 包含设备方法的异步版本。这些方法允许对许多设备进行批处理,而同步方法可以为每个 API 请求处理一个设备。异步方法名称具有 Async 后缀,例如 claimAsync

异步 API 方法会在处理完成之前返回结果。异步方法还有助于您的应用(或工具)在用户等待长时间运行的操作完成时保持响应。您的应用应定期检查操作的状态。

运维

您可以使用 Operation 跟踪长时间运行的批量操作。如果对异步方法的成功调用,将在响应中返回对操作的引用。以下 JSON 代码段显示了调用 updateMetadataAsync 后的典型响应:

{
  "name": "operations/apibatchoperation/1234567890123476789"
}

每项操作都包含单个任务的列表。调用 operations.get 以查找有关操作中包含的任务的状态和结果的信息。以下代码段展示了如何执行此操作。在您自己的应用中,您需要处理所有错误。

Java

// Build out the request body to apply the same order number to a customer's
// purchase of 2 devices.
UpdateMetadataArguments firstUpdate = new UpdateMetadataArguments();
firstUpdate.setDeviceMetadata(metadata);
firstUpdate.setDeviceId(firstTargetDeviceId);

UpdateMetadataArguments secondUpdate = new UpdateMetadataArguments();
secondUpdate.setDeviceMetadata(metadata);
secondUpdate.setDeviceId(firstTargetDeviceId);

// Start the device metadata update.
UpdateDeviceMetadataInBatchRequest body = new UpdateDeviceMetadataInBatchRequest();
body.setUpdates(Arrays.asList(firstUpdate, secondUpdate));
Operation response = service
    .partners()
    .devices()
    .updateMetadataAsync(PARTNER_ID, body)
    .execute();

// Assume the metadata update started, so get the Operation for the update.
Operation operation = service.operations().get(response.getName()).execute();

.NET

// Build out the request body to apply the same order number to a customer's
// purchase of 2 devices.
var updates = new List<UpdateMetadataArguments>
{
    new UpdateMetadataArguments
    {
        DeviceMetadata = metadata,
        DeviceId = firstTargetDeviceId
    },
    new UpdateMetadataArguments
    {
        DeviceMetadata = metadata,
        DeviceId = secondTargetDeviceId
    }
};

// Start the device metadata update.
UpdateDeviceMetadataInBatchRequest body = new UpdateDeviceMetadataInBatchRequest
{
    Updates = updates
};
var response = service.Partners.Devices.UpdateMetadataAsync(body, PartnerId).Execute();

// Assume the metadata update started, so get the Operation for the update.
Operation operation = service.Operations.Get(response.Name).Execute();

Python

# Build out the request body to apply the same order number to a customer's
# purchase of 2 devices.
updates = [{'deviceMetadata':metadata,'deviceId':first_target_device_id},
    {'deviceMetadata':metadata,'deviceId':second_target_device_id}]

# Start the device metadata update.
response = service.partners().devices().updateMetadataAsync(
    partnerId=PARTNER_ID, body={'updates':updates}).execute()

# Assume the metadata update started, so get the Operation for the update.
operation = service.operations().get(name=response['name']).execute()

如需了解操作是否已完成,请检查操作中是否存在值为 truedone 字段。如果 done 缺失或 false,则操作仍在运行。

响应

操作完成后,API 会使用结果更新操作,即使所有任务或全部任务均未成功也是如此。response 字段是一个 DevicesLongRunningOperationResponse 对象,其中详细说明了操作中每个设备的处理情况。

检查 successCount 字段可以高效地找出是否有任务失败,并避免遍历大型结果列表。DevicesLongRunningOperationResponseperDeviceStatus 字段是一个 OperationPerDevice 实例列表,其中详细说明了操作中的每个设备。列表顺序与原始请求中的任务一致。

每个 OperationPerDevice 任务都包含一个 result 字段以及服务器收到的请求的提醒摘要。使用 result 字段检查任务是成功还是失败。

以下 JSON 代码段显示了调用 updateMetadataAsync 后某个操作的典型响应的一部分:

"response": {
  "perDeviceStatus": [
    {
      "result": {
        "deviceId": "12345678901234567",
        "status": "SINGLE_DEVICE_STATUS_SUCCESS"
      },
      "updateMetadata": {
        "deviceId": "12345678901234567",
        "deviceMetadata": {
          "entries": {
            "phonenumber": "+1 (800) 555-0100"
          }
        }
      }
    }
  ],
  "successCount": 1
}

跟踪进度

如果您的应用需要跟踪进度,您应该定期重新获取操作。metadata 字段包含一个 DevicesLongRunningOperationMetadata 实例,可帮助应用检查正在运行的操作的最新进度。请使用下表中列出的 DevicesLongRunningOperationMetadata 字段跟踪操作的进度:

字段 典型用法
processingStatus 随着操作的进行,从 BATCH_PROCESS_PENDING 更改为 BATCH_PROCESS_IN_PROGRESS,然后更改为 BATCH_PROCESS_PROCESSED
progress 已处理的更新所占的百分比。您的应用可以使用此数据来估算完成时间。由于操作完成时 progress 值可能为 100,因此请检查操作的 done 字段以了解操作是否已完成并有结果。
devicesCount 显示操作中的更新次数。如果 API 无法解析某些更新,此值可能与请求中的更新次数不同。

下面的简化示例展示了应用如何使用进度元数据来设置轮询间隔。在您的应用中,您可能需要使用更复杂的任务运行程序来进行轮询。您还需要添加错误处理。

Java

// Milliseconds between polling the API.
private static long MIN_INTERVAL = 2000;
private static long MAX_INTERVAL = 10000;

// ...
// Start the device metadata update.
Operation response = service
    .partners()
    .devices()
    .updateMetadataAsync(PARTNER_ID, body)
    .execute();
String operationName = response.getName();

// Start polling for completion.
long startTime = new Date().getTime();
while (true) {

  // Get the latest update on the operation's progress using the API.
  Operation operation = service.operations().get(operationName).execute();

  if (operation.get("done") != null && operation.getDone()) {
    // The operation is finished. Print the status.
    System.out.format("Operation complete: %s of %s successful device updates\n",
        operation.getResponse().get("successCount"),
        operation.getMetadata().get("devicesCount"));
    break;

  } else {
    // Estimate how long the operation *should* take - within min and max value.
    BigDecimal opProgress = (BigDecimal) operation.getMetadata().get("progress");
    double progress = opProgress.longValue();
    long interval = MAX_INTERVAL;
    if (progress > 0) {
      interval = (long) ((new Date().getTime() - startTime) *
          ((100.0 - progress) / progress));
    }
    interval = Math.max(MIN_INTERVAL, Math.min(interval, MAX_INTERVAL));

    // Sleep until the operation should be complete.
    Thread.sleep(interval);
  }
}

.NET

// Milliseconds between polling the API.
private static double MinInterval = 2000;
private static double MaxInterval = 10000;

// ...
// Start the device metadata update.
var response = service.Partners.Devices.UpdateMetadataAsync(body, PartnerId).Execute();
var operationName = response.Name;

// Start polling for completion.
var startTime = DateTime.Now;
while (true)
{

    // Get the latest update on the operation's progress using the API.
    Operation operation = service.Operations.Get(operationName).Execute();

    if (operation.Done == true)
    {
        // The operation is finished. Print the status.
        Console.WriteLine("Operation complete: {0} of {1} successful device updates",
                          operation.Response["successCount"],
                          operation.Metadata["devicesCount"]);
        break;
    }
    else
    {
        // Estimate how long the operation *should* take - within min and max value.
        double progress = (double)(long)operation.Metadata["progress"];
        double interval = MaxInterval;
        if (progress > 0)
        {
            interval = DateTime.Now.Subtract(startTime).TotalMilliseconds *
                                     ((100.0 - progress) / progress);
        }
        interval = Math.Max(MinInterval, Math.Min(interval, MaxInterval));

        // Sleep until the operation should be complete.
        System.Threading.Thread.Sleep((int)interval);
    }
}

Python

# Seconds between polling the API.
MIN_INTERVAL = 2;
MAX_INTERVAL = 10;

# ...
# Start the device metadata update
response = service.partners().devices().updateMetadataAsync(
  partnerId=PARTNER_ID, body={'updates':updates}).execute()

op_name = response['name']
start_time = time.time()

# Start polling for completion
while True:
  # Get the latest update on the operation's progress using the API
  op = service.operations().get(name=op_name).execute()

  if 'done' in op and op['done']:
    # The operation is finished. Print the status.
    print('Operation complete: {0} of {1} successful device updates'.format(
      op['response']['successCount'], op['metadata']['devicesCount']
    ))
    break
  else:
    # Estimate how long the operation *should* take - within min and max.
    progress = op['metadata']['progress']
    interval = MIN_INTERVAL
    if progress > 0:
      interval = (time.time() - start_time) * ((100.0 - progress) / progress)
    interval = max(MIN_INTERVAL, min(interval, MAX_INTERVAL))

    # Sleep until the operation should be complete.
    time.sleep(interval)

选择对应用用户有意义的轮询方法。某些应用在等待某个流程完成时可能会受益于定期进度更新。

分页结果

partners.devices.findByOwner API 方法可能会返回非常大的设备列表。为了减小响应大小,此方法和其他 API 方法(如 partners.devices.findByIdentifier)支持分页结果。通过分页结果,您的应用可以迭代地请求和处理大型列表,一次一页。

调用 API 方法后,检查响应是否包含 nextPageToken 的值。如果 nextPageToken 不为 null,您的应用可以通过再次调用该方法,使用它来提取设备的另一页。您需要在 limit 参数中设置设备数量上限。如果 nextPageTokennull,则表示您的应用请求了最后一个页面。

以下示例方法展示了您的应用如何才能逐页输出设备列表:

Java

private static long MAX_PAGE_SIZE = 10;

// ...
/**
 * Demonstrates how to loop through paginated lists of devices.
 * @param pageToken       The token specifying which result page to return.
 * @throws IOException    If the zero-touch API call fails.
 */
private void printDevices(String pageToken) throws IOException {

  // Create the request body to find the customer's devices.
  FindDevicesByOwnerRequest body = new FindDevicesByOwnerRequest();
  body.setLimit(MAX_PAGE_SIZE);
  body.setSectionType("SECTION_TYPE_ZERO_TOUCH");
  body.setCustomerId(Collections.singletonList(targetCustomerId));

  // Call the API to get a page of Devices. Send a page token from the method
  // argument (might be None). If the page token is None, the API returns the first page.
  FindDevicesByOwnerResponse response =
      service.partners().devices().findByOwner(PARTNER_ID, body).execute();
  if (response.getDevices() == null) {
    return;
  }

  // Print the devices included in this page of results.
  for (Device device: response.getDevices()) {
    System.out.format("Device %s\n", device.getName());
  }
  System.out.println("---");

  // Check to see if another page of devices is available. If yes,
  // fetch and print the devices.
  if (response.getNextPageToken() != null) {
    this.printDevices(response.getNextPageToken());
  }
}

// ...
// Pass null to start printing the first page of devices.
printDevices(null);

.NET

private static int MaxPageSize = 10;

// ...
/// <summary>Demonstrates how to loop through paginated lists of devices.</summary>
/// <param name="pageToken">The token specifying which result page to return.</param>
private void PrintDevices(string pageToken)
{
    // Create the request body to find the customer's devices.
    FindDevicesByOwnerRequest body = new FindDevicesByOwnerRequest
    {
        PageToken = pageToken,
        Limit = MaxPageSize,
        SectionType = "SECTION_TYPE_ZERO_TOUCH",
        CustomerId = new List<long?>
        {
            targetCustomerId
        }
    };

    // Call the API to get a page of Devices. Send a page token from the method
    // argument (might be None). If the page token is None, the API returns the first page.
    var response = service.Partners.Devices.FindByOwner(body, PartnerId).Execute();
    if (response.Devices == null)
    {
        return;
    }

    // Print the devices included in this page of results.
    foreach (Device device in response.Devices)
    {
        Console.WriteLine("Device: {0}", device.Name);
    }
    Console.WriteLine("---");

    // Check to see if another page of devices is available. If yes,
    // fetch and print the devices.
    if (response.NextPageToken != null)
    {
        this.PrintDevices(response.NextPageToken);
    }
}

// ...
// Pass null to start printing the first page of devices.
PrintDevices(null);

Python

MAX_PAGE_SIZE = 10;

# ...
def print_devices(page_token):
  """Demonstrates how to loop through paginated lists of devices.

  Args:
    page_token: The token specifying which result page to return.
  """

   # Create the body to find the customer's devices.
  request_body = {'limit':MAX_PAGE_SIZE, \
    'pageToken':page_token, \
    'customerId':[target_customer_id], \
    'sectionType':'SECTION_TYPE_ZERO_TOUCH'}

  # Call the API to get a page of Devices. Send a page token from the method
  # argument (might be None). If the page token is None,
  # the API returns the first page.
  response = service.partners().devices().findByOwner(partnerId=PARTNER_ID,
    body=request_body).execute()

  # Print the devices included in this page of results.
  for device in response['devices']:
    print 'Device: {0}'.format(device['name'])
  print '---'

  # Check to see if another page of devices is available. If yes,
  # fetch and print the devices.
  if 'nextPageToken' in response:
    print_devices(response['nextPageToken'])

# ...
# Pass None to start printing the first page of devices.
print_devices(None);

后续步骤

现在您已了解该 API 的工作原理,不妨参阅 Java.NETPython 快速入门,尝试执行相关示例。