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, .NET, or Python.
API concepts
Customers and devices are the core resources you use in the API. To create
customers, call create
. You can create devices
using the claim API methods (see below). Your organization can also
create customers and devices using the zero-touch enrollment portal.
- Customer
- Companies that your organization sells devices to. Customers have a
name
and anID
. Use a customer when you want to claim or find their devices. To learn more, seeCustomer
. - 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
. - DeviceIdentifier
- Encapsulates hardware IDs, such as IMEI or MEID, to identify a manufactured
device. Use a
DeviceIdentifier
to target the device you want to find, update, or claim. To learn more, read Identifiers. - DeviceMetadata
- Stores key-value pairs of metadata for the device. Use
DeviceMetadata
to store your organization's metadata. To learn more, read Device metadata.
To list all the API methods and resources your app can use, see the API Reference.
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
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.
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
// 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()
To learn more about the owner and admin roles for employees of your customer, read 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
or
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) a customer's device before you can
claim the same device for a different customer. The claim methods
validate the DeviceIdentifier
fields,
including the IMEI or MEID, or the serial number, the
manufacturer name 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
// 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()
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
or
partners.devices.unclaimAsync
to unclaim a
device from a customer.
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.
Use the portal to create vendors for your
organization — you can’t use the API. Your account role must be
Owner to create a new vendor. If your organization has vendors,
you can call partners.vendors.list
to list your
vendors and 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
// 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'])
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
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
// 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
Long-running batch operations
The API includes asynchronous versions of the device 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
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
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
// 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()
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
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
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
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
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
// 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)
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
API method
might return very large lists of devices. To reduce the response size, this and
other API methods (such as
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
. 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
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);
Next steps
Now that you know how the API works, try out the examples with a quickstart for Java, .NET, or Python. You can use a colab to view examples of API calls and experiment with calling the API yourself.