Uploading media items is a two-step process:

1. Upload the bytes of your media files to a Google Server using the [uploads endpoint](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#uploading-bytes). This returns an upload token which identifies the uploaded bytes.
2. Use a [batchCreate call](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#creating-media-item) with the upload token to create a media item in the user's Google Photos account.

These steps outline the process of uploading a single media item. If you are
uploading multiple media items (very likely for any production application),
review the [best practices for uploads](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#best-practices) to improve your
upload efficiency.

## Before you begin

### Required authorization scopes

Uploading media items to a user's library or album requires either the
`photoslibrary.appendonly` or the `photoslibrary` scope.

Media items can also be created using the `photoslibrary.sharing` scope. To
create items with the `photoslibrary.sharing` scope, you must first create an
album and mark it as shared using `shareAlbum`. You can then create media items
that are shared with the user in the album. You can't create items directly in
the user's library or in albums that your app hasn't shared.

When listing albums, the `isWriteable` property indicates whether your
application has access to create media in a particular album.

### Accepted file types and sizes

You can upload the file types listed in the table below.

| Media type | Accepted file types | Maximum file size |
|---|---|---|
| **Photos** | AVIF, BMP, GIF, HEIC, ICO, JPG, PNG, TIFF, WEBP, some RAW files. | 200 MB |
| **Videos** | 3GP, 3G2, ASF, AVI, DIVX, M2T, M2TS, M4V, MKV, MMV, MOD, MOV, MP4, MPG, MTS, TOD, WMV. | 20 GB |

## Step 1: Uploading bytes

Upload bytes to Google using upload requests. A successful upload request
returns an upload token in the form of a raw text string. Use these upload
tokens to create media items with the `batchCreate` call.

> [!NOTE]
> **Note:** All media items uploaded to Google Photos through the API are stored in full resolution at [original
> quality](https://support.google.com/photos/answer/6220791). If your uploads exceed 25MB per user, your application should remind the user that these uploads will count towards storage in their Google Account.

### REST

Include the following fields in the POST request header:

| Header fields ||
|---|---|
| `Content-type` | Set to `application/octet-stream`. |
| `X-Goog-Upload-Content-Type` | Recommended. Set to the MIME type of the bytes you're uploading. Common MIME types include `image/jpeg`, `image/png`, and `image/gif`. |
| `X-Goog-Upload-Protocol` | Set to `raw`. |

Here is a POST request header:

```
POST https://photoslibrary.googleapis.com/v1/uploads
Authorization: Bearer oauth2-token
Content-type: application/octet-stream
X-Goog-Upload-Content-Type: mime-type
X-Goog-Upload-Protocol: raw
```

In the request body, include the binary of the file:

```
media-binary-data
```

If this POST request is successful, an upload token which is in the form
of a raw text string, is returned as the response body. To create media
items, use these text strings in the `batchCreate` call.

```
upload-token
```

### Java

```java
// Open the file and automatically close it after upload
try (RandomAccessFile file = new RandomAccessFile(pathToFile, "r")) {
  // Create a new upload request
  UploadMediaItemRequest uploadRequest =
      UploadMediaItemRequest.newBuilder()
              // The media type (e.g. "image/png")
              .setMimeType(mimeType)
              // The file to upload
              .setDataFile(file)
          .build();
  // Upload and capture the response
  UploadMediaItemResponse uploadResponse = photosLibraryClient.uploadMediaItem(uploadRequest);
  if (uploadResponse.getError().isPresent()) {
    // If the upload results in an error, handle it
    Error error = uploadResponse.getError().get();
  } else {
    // If the upload is successful, get the uploadToken
    String uploadToken = uploadResponse.getUploadToken().get();
    // Use this upload token to create a media item
  }
} catch (ApiException e) {
  // Handle error
} catch (IOException e) {
  // Error accessing the local file
}
```

### PHP

```php
try {
    // Create a new upload request by opening the file
    // and specifying the media type (e.g. "image/png")
    $uploadToken = $photosLibraryClient->upload(file_get_contents($localFilePath), null, $mimeType);
} catch (\GuzzleHttp\Exception\GuzzleException $e) {
    // Handle error
}
```

The suggested file size for images is less than 50 MB. Files above 50 MB are
prone to performance issues.

The Google Photos Library API supports
[resumable uploads](https://developers.google.com/photos/library/legacy/guides/resumable-uploads.md.txt). A resumable
upload allows you to split a media file into multiple sections and upload one
section at a time.

## Step 2: Creating a media item

After uploading the bytes of your media files, you can create them as media
items in Google Photos using upload tokens. An upload token is valid
for one day after being created. A media item is always added to the user's
library. Media items can only be
[added to albums](https://developers.google.com/photos/library/legacy/guides/manage-albums.md.txt#creating-new-album)
created by your app. For more information, see [Authorization scopes](https://developers.google.com/photos/library/legacy/guides/authorization.md.txt).

To create new media items, call
[`mediaItems.batchCreate`](https://developers.google.com/photos/library/legacy/reference/rest/v1/mediaItems/batchCreate.md.txt)
by specifying a list of `newMediaItems`. Each `newMediaItem` contains an upload
token that's specified inside a `simpleMediaItem`, and an optional description
that is shown to the user.

The description field is restricted to 1000 characters and should only include
meaningful text created by users. For example, "*Our trip to the park* " or
"*Holiday dinner*". Do not include metadata such as filenames, programmatic
tags, or other automatically generated text.

For best performance, reduce the number of calls to `mediaItems.batchCreate`
you have to make by including multiple media items in one call. Always wait
until the previous request has completed before making a subsequent call for the
same user.

You can create a single media item or multiple media items in a user's library
by specifying the descriptions and corresponding upload tokens:

### REST

Here is the POST request header:

```
POST https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate
Content-type: application/json
Authorization: Bearer oauth2-token
```

The request body should specify a list of `newMediaItems`.

```restructuredtext
{
  "newMediaItems": [
    {
      "description": "item-description",
      "simpleMediaItem": {
        "fileName": "filename",
        "uploadToken": "upload-token"
      }
    }
   , ...
  ]
}
```

### Java

```java
try {
  // Create a NewMediaItem with the following components:
  // - uploadToken obtained from the previous upload request
  // - filename that will be shown to the user in Google Photos
  // - description that will be shown to the user in Google Photos
  NewMediaItem newMediaItem = NewMediaItemFactory
          .createNewMediaItem(uploadToken, fileName, itemDescription);
  List<NewMediaItem> newItems = Arrays.asList(newMediaItem);

  BatchCreateMediaItemsResponse response = photosLibraryClient.batchCreateMediaItems(newItems);
  for (NewMediaItemResult itemsResponse : response.getNewMediaItemResultsList()) {
    Status status = itemsResponse.getStatus();
    if (status.getCode() == Code.OK_VALUE) {
      // The item is successfully created in the user's library
      MediaItem createdItem = itemsResponse.getMediaItem();
    } else {
      // The item could not be created. Check the status and try again
    }
  }
} catch (ApiException e) {
  // Handle error
}
```

### PHP

```php
try {
    $newMediaItems = [];
    // Create a NewMediaItem with the following components:
    // - uploadToken obtained from the previous upload request
    // - filename that will be shown to the user in Google Photos
    // - description that will be shown to the user in Google Photos
    $newMediaItems[0] = PhotosLibraryResourceFactory::newMediaItemWithDescriptionAndFileName(
            $uploadToken, $itemDescription, $fileName);

    $response = $photosLibraryClient->batchCreateMediaItems($newMediaItems);
    foreach ($response->getNewMediaItemResults() as $itemResult) {
        $status = $itemResult->getStatus();
        if ($status->getCode() != Code::OK) {
            // Error while creating the item.
        }
    }
} catch (\Google\ApiCore\ApiException $e) {
    // Handle error
}
```

You can add media items to the library and to an album by specifying the
album `id`. For more information, see
[Create albums](https://developers.google.com/photos/library/legacy/guides/manage-albums.md.txt#creating-new-album).

Each album can contain up to 20,000 media items. Requests to create media
items in an album that would exceed this limit will fail.

### REST

```restructuredtext
{
  "albumId": "album-id",
  "newMediaItems": [
    {
      "description": "item-description",
      "simpleMediaItem": {
        "fileName": "filename",
        "uploadToken": "upload-token"
      }
    }
   , ...
  ]
}
```

### Java

```java
try {
  // Create new media items in a specific album
  BatchCreateMediaItemsResponse response = photosLibraryClient
      .batchCreateMediaItems(albumId, newItems);
  // Check the response
} catch (ApiException e) {
  // Handle error
}
```

### PHP

```php
try {
    $response = $photosLibraryClient->batchCreateMediaItems($newMediaItems, ['albumId' => $albumId]);
} catch (\Google\ApiCore\ApiException $e) {
    // Handle error
}
```

You can also specify `albumId` and `albumPosition` to
insert media items at a specific location in the album.

### REST

```restructuredtext
{
  "albumId": "album-id",
  "newMediaItems": [
    {
      "description": "item-description",
      "simpleMediaItem": {
        "fileName": "filename",
        "uploadToken": "upload-token"
      }
    }
    , ...
  ],
  "albumPosition": {
    "position": "after-media-item",
    "relativeMediaItemId": "media-item-id"
  }
}
```

### Java

```java
try {
  // Create new media items in a specific album, positioned after a media item
  AlbumPosition positionInAlbum = AlbumPositionFactory.createFirstInAlbum();
  BatchCreateMediaItemsResponse response = photosLibraryClient
      .batchCreateMediaItems(albumId, newItems, positionInAlbum);
  // Check the response
} catch (ApiException e) {
  // Handle error
}
```

### PHP

```php
try {
    $albumPosition = PhotosLibraryResourceFactory::albumPositionAfterMediaItem($mediaItemId);
    $response = $photosLibraryClient->batchCreateMediaItems($newMediaItems,
        ['albumId' => $albumId, 'albumPosition' => $albumPosition]);
} catch (\Google\ApiCore\ApiException $e) {
    // Handle error
}
```

For more details regarding positioning in albums, see
[Add enrichments](https://developers.google.com/photos/library/legacy/guides/add-enrichments.md.txt).

> [!WARNING]
> **Warning:** The `batchCreate` call restricts the number of media items created in one call to 50 media items. It's also not recommended that multiple `batchCreate` be called in parallel for the same user.

### Item creation response

The `mediaItems.batchCreate` call returns the result for each of the media items
you tried to create. The list of `newMediaItemResults` indicates the status and
includes the `uploadToken` for the request. A non-zero status code indicates an
error.

### REST

If all media items have been successfully created, the request returns
HTTP status `200 OK`. If some media items can't be created,
the request returns HTTP status `207 MULTI-STATUS` to indicate
partial success.

```restructuredtext
{
  "newMediaItemResults": [
    {
      "uploadToken": "upload-token",
      "status": {
        "message": "Success"
      },
      "mediaItem": {
        "id": "media-item-id",
        "description": "item-description",
        "productUrl": "https://photos.google.com/photo/photo-path",
        "mimeType": "mime-type",
        "mediaMetadata": {
          "width": "media-width-in-px",
          "height": "media-height-in-px",
          "creationTime": "creation-time",
          "photo": {}
        },
        "filename": "filename"
      }
    },
    {
      "uploadToken": "upload-token",
      "status": {
        "code": 13,
        "message": "Internal error"
      }
    }
  ]
}
```

### Java

```java
BatchCreateMediaItemsResponse response = photosLibraryClient.batchCreateMediaItems(newItems);

// The response contains a list of NewMediaItemResults
for (NewMediaItemResult result : response.getNewMediaItemResultsList()) {
  // Each result item is identified by its uploadToken
  String uploadToken = result.getUploadToken();
  Status status = result.getStatus();

  if (status.getCode() == Code.OK_VALUE) {
    // If the request is successful, a MediaItem is returned
    MediaItem mediaItem = result.getMediaItem();
    String id = mediaItem.getId();
    String productUrl = mediaItem.getProductUrl();
    // ...
  }
}
```

### PHP

```php
// The response from a call to batchCreateMediaItems returns a list of NewMediaItemResults
foreach ($response->getNewMediaItemResults() as $itemResult) {
    // Each result item is identified by its uploadToken
    $itemUploadToken = $itemResult->getUploadToken();
    // Verify the status of each entry to ensure that the item has been uploaded correctly
    $itemStatus = $itemResult->getStatus();
    if ($itemStatus->getCode() != Code::OK) {
        // Error when item is being created
    } else {
        // Media item is successfully created
        // Get the MediaItem object from the response
        $mediaItem = $itemResult->getMediaItem();
        // It contains details such as the Id of the item, productUrl
        $id = $mediaItem->getId();
        $productUrl = $mediaItem->getProductUrl();
        // ...
    }
}
```

If an item is successfully added, a `mediaItem` is returned that contains its
`mediaItemId`, `productUrl` and `mediaMetadata`. For more information, see
[Access media items](https://developers.google.com/photos/library/legacy/guides/access-media-items.md.txt).

If the media item is a video, it must be processed first. The `mediaItem`
contains a `status` inside its `mediaMetadata` that describes the processing
state of the video file. A newly uploaded file returns the `PROCESSING` status
first, before it is `READY` for use. For details, see
[Access media items](https://developers.google.com/photos/library/legacy/guides/access-media-items.md.txt).

If you encounter an error during this call, follow the
[Best practices](https://developers.google.com/photos/library/legacy/guides/best-practices.md.txt) and retry your request.
You may want to keep track of successful additions, so the image can be inserted
into the album at the correct position during the next request. For more
information, see
[Create albums](https://developers.google.com/photos/library/legacy/guides/manage-albums.md.txt#creating-new-album).

Results are always returned in the same order in which upload tokens were
submitted.

> [!NOTE]
> **Note:** The same media item (defined as the same bytes being uploaded) is treated as the same object. This means that the same media item `id` is returned, even if it's referred to by different `uploadToken`.

## Best practices for uploads

The following best practices and resources help improve your overall efficiency
with uploads:

- Use one of our supported [client libraries](https://developers.google.com/photos/library/legacy/guides/client-libraries.md.txt).
- Follow the [retry and error handling best practices](https://developers.google.com/photos/library/legacy/guides/best-practices.md.txt#error-handling), keeping the following points in mind:
  - `429` errors can occur when [your quota has been exausted](https://developers.google.com/photos/library/legacy/guides/api-limits-quotas.md.txt) or you are rate limited for making too many calls too quickly. Make sure you do not call `batchCreate` for the same user until the previous request has completed.
  - `429` errors require a minimum `30s` delay before retrying. Use an [exponential backoff](https://developers.google.com/photos/library/legacy/guides/best-practices.md.txt#exponential-backoff) strategy when retrying requests.
  - `500` errors occur when the server encounters an error. When uploading, this is most likely because of making multiple write calls (such as `batchCreate`) for the same user at the same time. Check the details of your request and do not make calls to `batchCreate` in parallel.
- Use [the resumable upload flow](https://developers.google.com/photos/library/legacy/guides/resumable-uploads.md.txt) to make your uploads more robust in the case of network interruptions, reducing bandwidth usage by allowing you to resume partically completed uploads. This is important when uploading from client mobile devices, or when uploading large files.

As well, consider the following tips for each step of the upload process:
[uploading bytes](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#uploading-bytes-bp) and then [creating media items](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#creating-media-bp).

### Uploading bytes

- Uploading bytes (to retrieve upload tokens) can be done in parallel.
- Always set the correct MIME type in [the `X-Goog-Upload-Content-Type` header](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#X-Goog-Upload-Content-Type) for each upload call.

### Creating media items

- Do not make calls in parallel to `batchCreate` for a single user.

  - For each user, make calls to `batchCreate` one after another (in serial).
  - For multiple users, always make `batchCreate` calls for each user one after another. Only make calls for *different users* in parallel.
- Include [as many `NewMediaItems` as possible](https://developers.google.com/photos/library/legacy/reference/rest/v1/mediaItems/batchCreate.md.txt#body.request_body.FIELDS.new_media_items)
  in each call to `batchCreate` to minimize the total number of calls you have
  to make. At most you can include 50 items.

- Set a meaningful [description text](https://developers.google.com/photos/library/legacy/reference/rest/v1/mediaItems/batchCreate.md.txt#NewMediaItem.FIELDS.description)
  that has been created by your users. Do not include metadata such as
  filenames, programmatic tags, or other automatically generated text in the
  description field.

### Example walkthrough

This example uses pseudocode to walk through uploading media items for multiple
users. The goal is to outline both steps of the upload process ([uploading raw
bytes](https://developers.google.com/photos/library/legacy/guides/upload-media#uploading-bytes) and [creating media items](https://developers.google.com/photos/library/legacy/guides/upload-media.md.txt#creating-media-item)) and
detail the best practices for building an efficient and resilient upload
integration.

#### Step 1: Upload raw bytes

First create a queue to upload the raw bytes for your media items from all your
users. Track each returned `uploadToken` per user. Remember these key points:

- The number of simultaneous upload threads depends on your operating environment.
- Consider reordering the upload queue as needed. For example, you could prioritize uploads based on the number of remaining uploads per user, a user's overall progress, or other requirements.

### Pseudocode

```
CREATE uploadQueue FROM users, filesToUpload
// Upload media bytes in parallel.
START multiple THREADS
  WHILE uploadQueue is not empty
    POP uploadQueue
    UPLOAD file for user
    GET uploadToken
    CHECK and HANDLE errors
    STORE uploadToken for user in uploadTokensQueue
  END
```

#### Step 2: Create media items

In step 1, you can upload multiple bytes from multiple users in parallel, but
in step 2 step you can only make a single call for each user at a time.

### Pseudocode

```
// For each user, create media items once 50 upload tokens have been
// saved, or no more uploads are left per user.
WHEN uploadTokensQueue for user is >= 50 OR no more pending uploads for user
  // Calls can be made in parallel for different users,
  // but only make a single call per user at a time.
  START new thread for (this) user if there is no thread yet
    POP 50 uploadTokens from uploadTokensQueue for user
    CALL mediaItems.batchCreate with uploadTokens
    WAIT UNTIL batchCreate call has completed
    CHECK and HANDLE errors (retry as needed)
  DONE.
```

Continue this process until all uploads and media creation calls have completed.