CasCap.Api.GooglePhotos
4.1.0
Prefix Reserved
dotnet add package CasCap.Api.GooglePhotos --version 4.1.0
NuGet\Install-Package CasCap.Api.GooglePhotos -Version 4.1.0
<PackageReference Include="CasCap.Api.GooglePhotos" Version="4.1.0" />
<PackageVersion Include="CasCap.Api.GooglePhotos" Version="4.1.0" />
<PackageReference Include="CasCap.Api.GooglePhotos" />
paket add CasCap.Api.GooglePhotos --version 4.1.0
#r "nuget: CasCap.Api.GooglePhotos, 4.1.0"
#:package CasCap.Api.GooglePhotos@4.1.0
#addin nuget:?package=CasCap.Api.GooglePhotos&version=4.1.0
#tool nuget:?package=CasCap.Api.GooglePhotos&version=4.1.0
CasCap.Api.GooglePhotos
An unofficial .NET 10/11 client for the Google Photos Library API and Picker API. It provides typed models, OAuth authentication, paging, streaming uploads and optional client-side write rate limiting.
Google API Access Limits
Since 31 March 2025, the Library API only manages albums and media items created by your application. It cannot enumerate or back up a user's whole library, and Google withdrew Library API sharing. Use the Picker API when a user must interactively choose existing photos and videos, one session at a time. These are Google service restrictions, not limitations that a different client can bypass. Verified against Google's API update on 20 October 2026.
Installation
Targets net10.0 and net11.0.
dotnet package add CasCap.Api.GooglePhotos
dotnet package add Microsoft.Extensions.Hosting
Quick Start
Enable the relevant Google Photos API in Google Cloud, configure the OAuth consent screen and
create a Desktop app OAuth client. Photos requires user authorization; service accounts are not
supported. LoginAsync uses the installed-app browser/loopback flow, so complete initial consent
where the browser and callback are available. Configure test users and OAuth verification as
required by Google's policies.
After supplying the configuration below through a secret-backed provider:
using CasCap.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddGooglePhotos(builder.Configuration);
using var host = builder.Build();
var photos = host.Services.GetRequiredService<GooglePhotosService>();
if (!await photos.LoginAsync())
throw new InvalidOperationException("Google Photos authentication failed.");
var album = await photos.GetOrCreateAlbumAsync("Application uploads")
?? throw new InvalidOperationException("Album creation failed.");
var result = await photos.UploadSingleAsync(mediaPath, album.Id)
?? throw new InvalidOperationException("Media upload failed.");
mediaPath must name an existing supported media file. This example creates remote content and
requires AppendOnly plus ReadOnlyAppCreatedData. Uploads transfer bytes for an upload token, then
create the media item; UploadSingleAsync combines both stages. Inspect returned creation results
for API-level status rather than assuming every item in a batch succeeded.
Services
| Type | Purpose |
|---|---|
GooglePhotosService |
Library API client: albums, enrichments, media item search, uploads and downloads for application-created content |
GooglePhotosPickerService |
Picker API client: picking sessions, selected media listing and authenticated media streaming |
GooglePhotosCredentialProvider |
Singleton holding the OAuth grant shared by both clients; applied and refreshed per request |
GooglePhotosOptions |
Endpoints, OAuth credentials, scopes, timeouts and write rate-limit configuration |
GooglePhotosException |
Wraps errors returned by either API |
Call LoginAsync once on either client. The grant is stored in GooglePhotosCredentialProvider, so it applies to every resolved client and its access token is refreshed automatically.
Registration
AddGooglePhotos registers both clients, their HttpClient configuration, the standard resilience pipeline and the optional write rate limiter.
Overloads accept an IConfiguration section, a GooglePhotosOptions instance, or an Action<GooglePhotosOptions> delegate. All three validate the resulting options.
The configuration overload accepts the full IConfiguration and an optional section name, defaulting
to CasCap:GooglePhotosOptions. The options-instance overload copies options, including scopes and
rate-limit settings, rather than retaining their mutable collections.
Configuration Examples
Options bind from the CasCap:GooglePhotosOptions configuration section. Environment variables use the standard double-underscore form, for example CasCap__GooglePhotosOptions__ClientId.
Minimal configuration:
{
"CasCap": {
"GooglePhotosOptions": {
"User": "local-user",
"Scopes": ["AppendOnly", "ReadOnlyAppCreatedData"],
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
}
}
}
Complete configuration:
{
"CasCap": {
"GooglePhotosOptions": {
"BaseAddress": "https://photoslibrary.googleapis.com/v1/",
"PickerBaseAddress": "https://photospicker.googleapis.com/v1/",
"User": "local-user",
"Scopes": [
"AppendOnly",
"ReadOnlyAppCreatedData",
"EditAppCreatedData",
"PickerMediaItemsReadOnly"
],
"FileDataStoreFullPathOverride": null,
"RequestTimeoutSeconds": 90,
"UploadTimeoutSeconds": 3600,
"UploadRetryLimit": 10,
"WriteRateLimit": {
"Enabled": false,
"PermitLimit": 8,
"QueueLimit": 100,
"SegmentsPerWindow": 6,
"WindowSeconds": 60
},
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
}
}
}
| Key | Purpose |
|---|---|
BaseAddress |
Library API endpoint |
PickerBaseAddress |
Picker API endpoint |
User |
Local token-cache key, not necessarily the Google account email address |
Scopes |
OAuth scopes to request; request only what the application needs |
ClientId, ClientSecret |
Desktop app OAuth client credentials |
FileDataStoreFullPathOverride |
Optional directory for the cached OAuth grant |
RequestTimeoutSeconds |
Per-attempt timeout for non-upload API requests |
UploadTimeoutSeconds |
Timeout for each upload HTTP request, not a whole multipart file |
UploadRetryLimit |
Attempt limit per resumable multipart chunk |
WriteRateLimit |
Optional client-side sliding-window limiter for mutating Library API requests |
The examples contain placeholders; supply your own nonempty User, ClientId and ClientSecret.
User is a local cache key and need not be an email address. Keep credentials in .NET User Secrets
or a secret-backed provider, and protect the cached OAuth grant outside the repository.
Authentication and Request Behavior
Cached grants are isolated by local user, OAuth client ID and requested scopes. By default Google's
authentication library stores them under the user's application-data directory in Google.Apis.Auth;
FileDataStoreFullPathOverride selects another directory. If a required scope is declined or a grant
is revoked, authenticate again after removing/revoking the unusable grant.
SetAuth("Bearer", accessToken) accepts an externally acquired user token shared by both clients,
but does not refresh it; the caller owns expiry and replacement. Automatic refresh applies to
the OAuth credential obtained through LoginAsync.
The DI registration adds the standard resilience pipeline, with automatic retries disabled for
unsafe HTTP methods and up to six retries for safe methods. RequestTimeoutSeconds is the
per-attempt API timeout; the non-upload total budget is seven times that value. Each upload HTTP
request uses UploadTimeoutSeconds for both its attempt and total handler budget. Resumable
multipart chunk recovery uses UploadRetryLimit, rather than blindly replaying mutating requests.
The optional oldest-first sliding-window write limiter is disabled by default. Its limits belong to the handler pipeline, not Google's authoritative quota, and are not coordinated across separate containers or application instances. Reads, including the read-only Library search POST, and Picker requests bypass it.
Logging never includes page tokens, media item or album identifiers, filenames, titles or media
metadata. Page requests log only their base path and page size at Debug, and access-token refreshes
and individual failed upload requests log at Debug because the outcome is retried or surfaces as a
GooglePhotosException. GetMediaItemsByIdsAsync logs each unavailable item's status at Debug and
one Warning with the unavailable and requested counts when enumeration completes.
Picker Usage
Request PickerMediaItemsReadOnly and resolve GooglePhotosPickerService from the same container.
Call LoginAsync once, then:
CreateSessionAsyncreturns aPickerUriand polling configuration.- Open that URI outside an iframe in a browser signed into the session's Google account.
- Poll
GetSessionAsyncat Google'sPollInterval, respectingTimeoutIn. - Once
MediaItemsSetis true, enumerateGetMediaItemsAsync(sessionId). - Stream selected bytes with
DownloadPhotoAsyncorDownloadVideoAsync, then delete the session.
Picked-media URLs normally expire after 60 minutes, can expire sooner on revocation, and require bearer authorization. The download helpers use the authenticated client. Photo downloads require width and height between 1 and 16383 and can request EXIF metadata.
See the Picker walkthrough and interaction diagram and Google's media access guide.
Dependency Graph
Arrows show direct runtime package references. Samples and tests have additional dependencies; the shared HTTP package supplies transitive serialization infrastructure.
graph LR
photos["CasCap.Api.GooglePhotos"] --> auth["Google.Apis.Auth"]
photos --> web["Microsoft.AspNetCore.WebUtilities"]
photos --> resilience["Microsoft.Extensions.Http.Resilience"]
photos --> binding["Microsoft.Extensions.Options.ConfigurationExtensions"]
photos --> validation["Microsoft.Extensions.Options.DataAnnotations"]
photos --> mime["MimeTypeMapOfficial"]
photos --> limiting["System.Threading.RateLimiting"]
photos --> net["CasCap.Common.Net"]
classDef owned fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef published fill:#dcfce7,stroke:#15803d,color:#14532d
class photos owned
class auth,web,resilience,binding,validation,mime,limiting,net published
The blue node is this package; green nodes are external packages.
| Package | Purpose |
|---|---|
Google.Apis.Auth |
OAuth authentication and cached token grants |
Microsoft.AspNetCore.WebUtilities |
Query-string construction |
Microsoft.Extensions.Http.Resilience |
HTTP timeouts, circuit breaking and safe retries |
Microsoft.Extensions.Options.ConfigurationExtensions |
Options binding |
Microsoft.Extensions.Options.DataAnnotations |
Options validation |
MimeTypeMapOfficial |
Upload MIME-type detection |
System.Threading.RateLimiting |
Optional temporal Library API write limiting |
CasCap.Common.Net |
Shared HTTP and serialization infrastructure |
Documentation
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- CasCap.Common.Net (>= 5.2.0)
- Google.Apis.Auth (>= 1.78.0)
- Microsoft.AspNetCore.WebUtilities (>= 10.0.15)
- Microsoft.Extensions.Http.Resilience (>= 10.11.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.15)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.15)
- MimeTypeMapOfficial (>= 1.0.17)
- System.Threading.RateLimiting (>= 10.0.15)
-
net11.0
- CasCap.Common.Net (>= 5.2.0)
- Google.Apis.Auth (>= 1.78.0)
- Microsoft.AspNetCore.WebUtilities (>= 11.0.0)
- Microsoft.Extensions.Http.Resilience (>= 11.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 11.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 11.0.0)
- MimeTypeMapOfficial (>= 1.0.17)
- System.Threading.RateLimiting (>= 11.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.