## Introduction

The zero-touch enrollment API helps device resellers automate their integration.
Your organization's sales tools can build in zero-touch enrollment---making your
users, and your customers, more productive. Use the API to help your users:

- Assign purchased devices to a customer's zero-touch enrollment account.
- Create your customer's zero-touch enrollment account.
- Attach your organization's telephone and order metadata to devices.
- Create reports about devices assigned to your customers.

This document introduces the API and explains the patterns. If you want to
explore the API yourself, try a quickstart for
[Java](https://developers.google.com/zero-touch/guides/quickstart/java),
[.NET](https://developers.google.com/zero-touch/guides/quickstart/dotnet), or
[Python](https://developers.google.com/zero-touch/guides/quickstart/python).

## API concepts

Customers and devices are the core resources you use in the API. To create
customers, call [`create`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.customers/create). You can create devices
using the claim API methods ([see below](https://developers.google.com/zero-touch/guides/how-it-works#claim)). Your organization can also
create customers and devices using the zero-touch enrollment [portal](https://enterprise.google.com/android/zero-touch/resellers).

![Device and customer resource relationship](https://developers.google.com/static/zero-touch/images/guides/how-it-works/resources.svg)

Customer
:   Companies that your organization sells devices to. Customers have a `name`
    and an `ID`. Use a customer when you want to claim or find their devices. To
    learn more, see [`Customer`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.customers).

Device
:   A zero-touch enrollment-capable Android or ChromeOS device your organization
    sells to a customer. Devices have hardware IDs, metadata, and customer
    claims. Devices are central to the API, so you use them in almost all
    methods. To learn more, see [`Device`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices).

DeviceIdentifier
:   Encapsulates hardware IDs, such as IMEI or MEID, to identify a manufactured
    device. Use a [`DeviceIdentifier`](https://developers.google.com/zero-touch/guides/DeviceIdentifier) to target the device
    you want to find, update, or claim. To learn more, read
    [Identifiers](https://developers.google.com/zero-touch/guides/identifiers).

DeviceMetadata
:   Stores key-value pairs of metadata for the device. Use
    [`DeviceMetadata`](https://developers.google.com/zero-touch/guides/DeviceMetadata) to store your organization's metadata. To
    learn more, read [Device metadata](https://developers.google.com/zero-touch/guides/metadata).

To list all the API methods and resources your app can use, see the
[API Reference](https://developers.google.com/zero-touch/reference/reseller/rest).

## Create customers

For Android devices, the reseller is responsible for creating the customer
account on behalf of their customer. The customer will use this account to
access the zero-touch portal to configure the provisioning settings for their
devices. This is not necessary for ChromeOS devices, who already have a Google
Workspace account which they will use to configure their provisioning settings.

You can call the [`create`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.customers/create) API method to create
customer accounts for zero-touch enrollment. Because your customers see the
company name in their zero-touch enrollment portal, your app's user should
confirm that it's correct. You can't edit a customer's name after you create the
customer.

You need to include at least one corporate email address, associated with a
Google Account, to be the owner. You can't use personal Gmail accounts with the
API. If the customer needs help with associating the account, send the
instructions from
[Associate a Google Account](https://developers.google.com/zero-touch/guides/portal#associate-account).

> [!NOTE]
> **Note:** The API returns an error when one or more of the email addresses isn't associated with a Google Account. When this happens, ask your user to [check that there's a Google Account](https://support.google.com/accounts/answer/40560) for each email address. Google doesn't provide an API to check if an email address is associated with a Google Account.

After you create a customer by calling the API, they manage their employees'
portal access --- you can't edit your customers' users using the API. The snippet
below shows how you might create a customer:

### Java

```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

```c#
// 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

```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()
```

To learn more about the owner and admin roles for employees of your customer,
read [Portal users](https://developers.google.com/zero-touch/guides/portal#users).

## Claim devices for customers

After your customers purchase devices, they'll want to configure provisioning
settings for these devices in their account. Claiming a device adds the device
to zero-touch enrollment and gives the customer the ability to configure
provisioning settings.

A device's provisioning record has a section for zero-touch enrollment. You
assign the device by claiming the record's zero-touch enrollment section for a
customer. Call the [`partners.devices.claim`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/claim) or
[`partners.devices.claimAsync`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/claimAsync) methods with the
customer as an argument. Always supply `SECTION_TYPE_ZERO_TOUCH` as a value for
`sectionType`.

You'll need to unclaim ([see below](https://developers.google.com/zero-touch/guides/how-it-works#claim)) a customer's device before you can
claim the same device for a different customer. The claim methods
[validate](https://developers.google.com/zero-touch/guides/identifiers) the [`DeviceIdentifier`](https://developers.google.com/zero-touch/guides/DeviceIdentifier) fields,
including the IMEI or MEID, or the serial number, the
[manufacturer name](https://developers.google.com/zero-touch/resources/manufacturer-names) and model, and the
attested device ID for ChromeOS devices, when creating a new device.

The snippet below shows how to claim a device:

### Java

```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

```c#
// 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

```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()
```

### Unclaiming devices

Your organization can unclaim a device from a customer. Unclaiming a device
removes it from zero-touch enrollment. A reseller might unclaim a device that
they want migrated to another account, returned, or that was mistakenly claimed.
Call the method [`partners.devices.unclaim`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/unclaim) or
[`partners.devices.unclaimAsync`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/unclaimAsync) to unclaim a
device from a customer.

> [!CAUTION]
> **Caution:** Consider prompting your app's user to confirm that they have the correct device before calling unclaim APIs. Unclaiming a device removes the customer's provisioning setup and the device becomes unmanaged after a factory reset.

## Vendors

You can use vendors to represent reseller partners in your dealer network, local
operators within a global reseller network, or any organization that sells
devices on your behalf. Vendors help you separate your users, customers, and
devices:

- Vendors you create can't see your zero-touch enrollment account or each others' accounts.
- You can view your vendors' customers and devices and you can unregister vendors' devices. However, you can't assign devices to your vendors' customers.

> [!IMPORTANT]
> **Key Point:** A **vendor** is a special type of reseller. Vendors appear and behave (in the portal and APIs) just as a reseller does---except that a vendor can't have other vendors.

Use the portal to [create vendors](https://developers.google.com/zero-touch/guides/portal#vendors) for your
organization --- you can't use the API. Your account role must be
[*Owner*](https://developers.google.com/zero-touch/guides/user_roles) to create a new vendor. If your organization has vendors,
you can call [`partners.vendors.list`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.vendors/list) to list your
vendors and [`partners.vendors.customers.list`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.vendors.customers/list)
to get your vendor's customers. The following example uses both of these methods
to print a report showing the Terms of Service status for the vendors'
customers:

### Java

```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

```c#
// 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

```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'])
```

If you have a collection of devices, you might need to know which reseller or
vendor claimed the device. To get the numeric reseller ID, inspect the value of
the [`resellerId`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices#DeviceClaim.FIELDS.reseller_id) field in a device's claim record.

Your organization can unclaim a vendor-claimed device. For other API calls that
modify devices, you should check that your organization claimed the device
before calling the API method. The following example shows how you can do this:

### Java

```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

```c#
// 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

```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
```

## Long-running batch operations

The API includes asynchronous versions of the [device methods](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices#methods).
These methods allow batch processing of many devices, while the synchronous
methods process one device for each API request. The asynchronous method names
have an *Async* suffix, for example `claimAsync`.

Asynchronous API methods return a result before the processing is complete.
Asynchronous methods also help your app (or tool) remain responsive for your
users while they wait for a long-running operation to complete. Your app should
check the status of the operation periodically.

### Operations

You use an [`Operation`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/operations) to track a long-running batch operation. A
successful call to an asynchronous method returns a reference to the operation
in the response. The JSON snippet below shows a typical response after calling
`updateMetadataAsync`:

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

Each operation contains a list of individual tasks. Call
[`operations.get`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/operations/get) to find out information about the status and
results of tasks contained in the operation. The snippet below shows how you
might do this. In your own app, you'll need to handle any errors.

### Java

```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

```c#
// 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

```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()
```

To find out if an operation finished, check the operation for a `done` field
with a value of `true`. If `done` is missing or `false`, the operation is still
running.

### Responses

After an operation finishes, the API updates the operation with the result---even
if all or none of the individual tasks are successful. The `response` field is a
[`DevicesLongRunningOperationResponse`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/DevicesLongRunningOperationResponse)
object detailing the processing of each device in the operation.

Inspect the `successCount` field to efficiently find out if any tasks failed and
avoid iterating through large result lists. The `perDeviceStatus` field of
`DevicesLongRunningOperationResponse` is a list of
[`OperationPerDevice`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/DevicesLongRunningOperationResponse#OperationPerDevice) instances detailing each device in
the operation. The list order matches the tasks in the original request.

Each `OperationPerDevice` task contains a `result` field and a reminder summary
of the request received by the server. Check if the task succeeded or failed
using the [`result`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/DevicesLongRunningOperationResponse#PerDeviceStatusInBatch) field.

The JSON snippet below shows part of a typical response from an operation after
a call to `updateMetadataAsync`:

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

### Track progress

If your app needs to track progress, you should periodically refetch the
operation. The `metadata` field contains a
[`DevicesLongRunningOperationMetadata`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/DevicesLongRunningOperationMetadata)
instance to help your app check the latest progress of a running operation. Use
the fields of `DevicesLongRunningOperationMetadata` listed in the following
table to track the operation's progress:

| Field | Typical use |
|---|---|
| `processingStatus` | Changes from `BATCH_PROCESS_PENDING` to `BATCH_PROCESS_IN_PROGRESS`, and then to `BATCH_PROCESS_PROCESSED` as the operation progresses. |
| `progress` | The percentage of updates processed. Your app can use this to estimate a finish time. Because the `progress` value can be *100* while the operation is finishing up, check the `done` field of an operation to know if it finished and has a result. |
| `devicesCount` | Shows the number of updates in the operation. This might be different from the number of updates in your request if the API can't parse some of the updates. |

The simplified example below shows how an app might use the progress metadata to
set polling intervals. In your app, you might need a more sophisticated task
runner for polling. You'll also need to add error handling.

### Java

```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

```c#
// 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

```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)
```

Choose a polling approach that makes sense for your app's users. Some app users
might benefit from regular progress updates if they're waiting for a process to
complete.

## Paged results

The [`partners.devices.findByOwner`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/findByOwner) API method
might return very large lists of devices. To reduce the response size, this and
other API methods (such as
[`partners.devices.findByIdentifier`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/findByIdentifier))
support paged results. With paged results, your application can iteratively
request and process large lists one page at a time.

After calling the API method, check if the response includes a value for
[`nextPageToken`](https://developers.google.com/zero-touch/reference/reseller/rest/v1/partners.devices/findByOwner#response-body). If `nextPageToken`
isn't `null`, your app can use it to fetch another page of devices by calling
the method again. You need to set an upper limit for the number of devices in
the `limit` parameter. If `nextPageToken` is `null`, your app has requested the
last page.

The example method below shows how your app might print a list of devices, one
page at a time:

### Java

```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

```c#
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

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

## Next steps

Now that you know how the API works, try out the examples with a quickstart for
[Java](https://developers.google.com/zero-touch/guides/quickstart/java),
[.NET](https://developers.google.com/zero-touch/guides/quickstart/dotnet), or
[Python](https://developers.google.com/zero-touch/guides/quickstart/python). You can use a
[colab](https://colab.sandbox.google.com/github/googlesamples/zero-touch-enrollment-colabs/blob/main/Zero_touch_enrollment_reseller_API.ipynb) to view
examples of API calls and experiment with calling the API yourself.