Upload options
The Android Over The Air API allows you to upload package data to create a new Package resource. These are OTA packages that can be associated with one or more configurations so that the update is delivered to devices.
We provide a binary for Linux and Windows to facilitate resumable package uploads which you are free to use instead of implementing the protocols described below. If you wish for a deeper integration, please use one of the protocols described below.
Linux
Download the Android Over The Air API v1 uploader client for Linux.
Windows
Download the Android Over The Air API v1 uploader client for Windows.
To use it, you first need to create a service account and obtain a JSON key file for that account.
Please see our guide for creating the account here.
Once you have the binary and the key file, you can run it with command line options to specify
the key file, your deployment and the package you're uploading. Please use --help
to see all the options.
Upload Protocols
You can make upload requests in any of the following ways.
Specify the method you are using with the X-Goog-Upload-Protocol
request header.
- Multipart upload:
X-Goog-Upload-Protocol: multipart
. For quick transfer of smaller files and metadata; transfers the file along with metadata that describes it, all in a single request. - Resumable upload:
X-Goog-Upload-Protocol: resumable
. For reliable transfer, especially important with larger files. With this method, you use a session initiating request, which optionally can include metadata. This is a good strategy to use for most applications, because it also works for smaller files at the cost of one additional HTTP request per upload.
Multipart upload
This is a good choice if the data you are sending is small enough to upload again in its entirety if the connection fails.
To use multipart upload, make a POST
request to the /upload/package
URI and set the X-Goog-Upload-Protocol
to multipart
.
The top-level HTTP headers to use when making a multipart upload request include:
Content-Type
. Set to multipart/related and include the boundary string you're using to identify the parts of the request.Content-Length
. Set to the total number of bytes in the request body.
The body of the request is formatted as a multipart/related
content
type [RFC2387] and contains exactly two parts.
The parts are identified by a boundary string, and the final boundary string is followed by two hyphens.
Each part of the multipart request needs an additional Content-Type
header:
- Metadata part: Must come first, and
Content-Type
must beapplication/json
. - Media part: Must come second, and
Content-Type
must beapplication/zip
.
Example: Multipart upload
The example below shows a multipart upload request for the Android Over The Air API.
POST /upload/package HTTP/1.1 Host: androidovertheair.googleapis.com Authorization: Bearer your_auth_token Content-Type: multipart/related; boundary=BOUNDARY Content-Length: number_of_bytes_in_entire_request_body --BOUNDARY Content-Type: application/json; charset=UTF-8 {"deployment": "id", "package_title": "title" } --BOUNDARY Content-Type: application/zip; charset=UTF-8 Package ZIP --BOUNDARY--
If the request succeeds, the server returns the HTTP 200 OK
status code
HTTP/1.1 200
A way to easily accomplish this is to use curl and oauth2l. Below is a sample request that assumes you're using a service key (see our authorization how to for more information).
Example curl request
JSON={"deployment": "id", "package_title": "title" } SERVICE_KEY_FILE=path to your service key json file curl \ -H "$(./oauth2l header --json $SERVICE_KEY_FILE android_partner_over_the_air)" \ -H "Host: androidovertheair.googleapis.com" \ -H "X-Goog-Upload-Protocol: multipart" \ -H "Content-Type: multipart/form-data" \ -F "json=$JSON;type=application/json" \ -F "data=@update.zip;type=application/zip" \ androidovertheair.googleapis.com/upload/package
Resumable upload
To upload data files more reliably, you can use the resumable upload protocol. This protocol allows you to resume an upload operation after a communication failure has interrupted the flow of data. It is especially useful if you are transferring large files and the likelihood of a network interruption or some other transmission failure is high, for example, when uploading from a mobile client app. It can also reduce your bandwidth usage in the event of network failures because you don't have to restart large file uploads from the beginning.
The resumable upload protocol uses several commands:
- Start a resumable session. Make an initial request to the upload URI that includes the metadata and establishes a unique resumable upload location.
- Save the resumable session URI. Save the session URI returned in the response of the initial request; you'll use it for the remaining requests in this session.
- Upload the file. Send all or part of the ZIP file to the resumable session URI.
In addition, apps that use resumable upload need to have code to resume an interrupted upload. If an upload is interrupted, find out how much data was successfully received, and then resume the upload starting from that point.
Note: An upload URI expires after 3 days.
Step 1: Start a resumable session
To initiate a resumable upload, make a POST
request to the /upload/package
URI and set the X-Goog-Upload-Protocol
to resumable
.
For this initiating request, the body must contains the metadata only; you'll transfer the actual contents of the file you want to upload in subsequent requests.
Use the following HTTP headers with the initial request:X-Goog-Upload-Header-Content-Type
. This is the content-type of the file being uploaded and must be set toapplication/zip
.X-Goog-Upload-Command
. Set tostart
X-Goog-Upload-Header-Content-Length
. Set to the number of bytes of upload data to be transferred in subsequent requests. If the length is unknown at the time of this request, you can omit this header.Content-Type
. This is the content-type of the metadata and must be set toapplication/json
.Content-Length
. Set to the number of bytes provided in the body of this initial request.
Example: Resumable session initiation request
The following example shows how to initiate a resumable session for the Android Over The Air API.
POST /upload/package HTTP/1.1 Host: android/over-the-air.googleapis.com Authorization: Bearer your_auth_token Content-Length: 38 Content-Type: application/json; charset=UTF-8 X-Goog-Upload-Command: start X-Goog-Upload-Header-Content-Type: application/zip X-Goog-Upload-Header-Content-Length: 2000000 {"deployment": "id", "package_title": "title" }
The next section describes how to handle the response.
Step 2: Save the resumable session URI
If the session initiation request succeeds, the API server responds with a HTTP 200 OK
status code.
In addition, it provides an X-Goog-Upload-URL
header that specifies your resumable session URI.
The X-Goog-Upload-URL
header, shown in the example below, includes an upload_id
query parameter
portion that gives the unique upload ID to use for this session. The response also contains an X-Goog-Upload-Status
header, which will be active
if the upload request was valid and accepted. This status may be final
if the upload was rejected.
Example: Resumable session initiation response
Here is the response to the request in Step 1:
HTTP/1.1 200 OK X-Goog-Upload-Status: active X-Goog-Upload-URL: androidovertheair.googleapis.com/?upload_id=xa298sd_sdlkj2 Content-Length: 0
The value of the X-Goog-Upload-URL
header, as shown in the above example response, is
the session URI you'll use as the HTTP endpoint for doing the actual file upload or querying the upload status.
Copy and save the session URI so you can use it for subsequent requests.
Step 3: Upload the file
To upload the file, send a POST
request to the upload URI that you obtained in the
previous step. The format of the upload request is:
POST session_uri
The HTTP headers to use when making the resumable file upload requests include:
Content-Length
. Set this to the number of bytes you are uploading in this request, which is generally the upload file size.X-Goog-Upload-Command
. Set this toupload
andfinalize
.X-Goog-Upload-Offset
. This specified the offset at which bytes should be written. Note that clients must upload bytes serially.
Example: Resumable file upload request
Here is a resumable request to upload the entire 2,000,000 byte ZIP file for the current example.
POST /?upload_id=xa298sd_sdlkj2 HTTP/1.1 Host: androidovertheair.googleapis.com X-Goog-Upload-Protocol: resumable X-Goog-Upload-Command: upload, finalize X-Goog-Upload-Offset: 0 Content-Length: 2000000 Content-Type: application/zip bytes 0-1999999
If the request succeeds, the server responds with an HTTP 200 Ok
.
If the upload request is interrupted or if you receive an HTTP 503 Service Unavailable
or any
other 5xx
response from the server, follow the procedure outlined in resume an interrupted upload.
Uploading the file in chunks
With resumable uploads, you can break a file into chunks and send a series of requests to upload each chunk in sequence.
This is not the preferred approach because there are performance costs associated with the additional requests, and it is
generally not needed. We recommend that clients upload all remaining bytes of the payload and
include the finalize
command with every upload
command.
However, you might need to use chunking to reduce the amount of data transferred in any single request. It also lets you do things like providing upload progress indications for legacy browsers that don't have upload progress support by default.
Resume an interrupted upload
If an upload request is terminated before receiving a response or if you receive an
HTTP 503 Service Unavailable
response from the server, then you need to resume the interrupted upload. To do this:
- Request status. Query the current status of the upload by issuing a request to the upload URI
with the
X-Goog-Upload-Command
set toquery
.Note: You can request the status between chunks, not just if the upload is interrupted. This is useful, for example, if you want to show upload progress indications for legacy browsers.
- Get number of bytes uploaded. Process the response from the status query. The server uses
the
X-Goog-Upload-Size-Received
header in its response to specify how many bytes it has received so far. - Upload remaining data. Finally, now that you know where to resume the request, send the
remaining data or current chunk. Note that you need to treat the remaining data as a separate chunk in either case, so
you need to set the
X-Goog-Upload-Offset
header to the proper offset when you resume the upload.
Example: Resuming an interrupted upload
1) Request the upload status.
POST /?upload_id=xa298sd_sdlkj2 HTTP/1.1 Host: androidovertheair.googleapis.com X-Goog-Upload-Command: query
As with all commands, the client must check the X-Goog-Upload-Status
header in the HTTP response of a query command.
If the header is present and the value is not active
, then the upload has already been terminated.
2) Extract the number of bytes uploaded so far from the response.
The server's response uses the X-Goog-Upload-Size-Received
header to indicate that it has
received the first 43 bytes of the file so far.
HTTP/1.1 200 OK X-Goog-Upload-Status: active X-Goog-Upload-Size-Received: 42
3) Resume the upload from the point where it left off.
The following request resumes the upload by sending the remaining bytes of the file, starting at byte 43.
POST /?upload_id=xa298sd_sdlkj2 HTTP/1.1 Host: androidovertheair.googleapis.com X-Goog-Upload-Command: upload, finalize Content-Length: 1999957 X-Goog-Upload-Offset: 43 bytes 43-1999999
Best practices
When uploading media, it is helpful to be aware of some best practices related to error handling.
- Resume or retry uploads that fail due to connection interruptions or any
5xx
errors, including:500 Internal Server Error
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
- Use an exponential backoff strategy if any
5xx
server error is returned when resuming or retrying upload requests. These errors can occur if a server is getting overloaded. Exponential backoff can help alleviate these kinds of problems during periods of high volume of requests or heavy network traffic. - Other kinds of requests should not be handled by exponential backoff, but you can still retry a number of them. When retrying these requests, limit the number of times you retry them. For example, your code could limit to 10 retries or less before reporting an error.
- Handle
404 Not Found
errors when doing resumable uploads by starting the entire upload over from the beginning.
Exponential backoff
Exponential backoff is a standard error handling strategy for network applications in which the client periodically retries a failed request over an increasing amount of time. If a high volume of requests or heavy network traffic causes the server to return errors, exponential backoff may be a good strategy for handling those errors. Conversely, it is not a relevant strategy for dealing with errors unrelated to network volume or response times, such as invalid authorization credentials or file not found errors.
Used properly, exponential backoff increases the efficiency of bandwidth usage, reduces the number of requests required to get a successful response, and maximizes the throughput of requests in concurrent environments.
The flow for implementing simple exponential backoff is as follows:
- Make a request to the API.
- Receive an
HTTP 503
response, which indicates you should retry the request. - Wait 1 second + random_number_milliseconds and retry the request.
- Receive an
HTTP 503
response, which indicates you should retry the request. - Wait 2 seconds + random_number_milliseconds, and retry the request.
- Receive an
HTTP 503
response, which indicates you should retry the request. - Wait 4 seconds + random_number_milliseconds, and retry the request.
- Receive an
HTTP 503
response, which indicates you should retry the request. - Wait 8 seconds + random_number_milliseconds, and retry the request.
- Receive an
HTTP 503
response, which indicates you should retry the request. - Wait 16 seconds + random_number_milliseconds, and retry the request.
- Stop. Report or log an error.
In the above flow, random_number_milliseconds is a random number of milliseconds less than or equal to 1000. This is necessary, because introducing a small random delay helps distribute the load more evenly and avoid the possibility of stampeding the server. The value of random_number_milliseconds must be redefined after each wait.
Note: The wait is always (2 ^ n) + random_number_milliseconds, where n is a monotonically increasing integer initially defined as 0. The integer n is incremented by 1 for each iteration (each request).
The algorithm is set to terminate when n is 5. This ceiling prevents clients from retrying infinitely, and results in a total delay of around 32 seconds before a request is deemed "an unrecoverable error." A larger maximum number of retries is fine, especially if a long upload is in progress; just be sure to cap the retry delay at something reasonable, say, less than one minute.