API reference
Photos
Your photo library: the timeline, photos, albums, links, uploads, Takeout imports, tokens, exports and your activity log.
See Photos. Every route is under /v1/me/photos: your own library, for you signed in, or your photo token (si_pho_…; photos:upload for uploads, photos:read for reading). Organization keys, Caity and the phone assistant never reach it. Links anyone can open are under /v1/hooks/photos without authentication.
Uploads: POST uploads (with the file's sha256 to skip one already in the library) answers the part size and links for the first parts; PUT each part's bytes to its link, ask POST uploads/:uploadId/parts for more, then POST uploads/:uploadId/complete. With singlePart: true the whole file goes to urls["1"] and size can be left out. Details and previews follow in the background (processed).
GET /v1/hooks/photos/links/:token
What a Photos link opens: its title, how many photos, whether locations are kept and downloads allowed, or that it needs its password. No authentication; the visit is in the owner's log.
Auth: none
| Path parameter | Description |
|---|---|
:token | A secret from a link: an issue automation rule's incoming-webhook token (shown once when issued), a map's share link, a notification recipient's confirm and unsubscribe link, a Drive public link (/l/<token>), or a Mirage interaction's token (itk_…, sent with the interaction, good for 15 minutes), or the token in the link to a file of a message Mirage sent to Discord, or a Marketing link's sealed token (a tracked click, the open pixel, unsubscribe and the preference centre, a double opt-in confirmation); or a Food recipe's public link (/r/<token>) or a household's calendar feed. |
Response 200
{
needsPassword: true
} | {
needsPassword: false
title: string
count: number
keepLocation: boolean
download: boolean
expiresAt?: number
owner?: string
}POST /v1/hooks/photos/links/:token/unlock
Checks a link's password and answers a 12-hour grant (send it as x-si-link-grant). 10 wrong passwords per link per 15 minutes lock it. No authentication.
Auth: none
| Path parameter | Description |
|---|---|
:token | A secret from a link: an issue automation rule's incoming-webhook token (shown once when issued), a map's share link, a notification recipient's confirm and unsubscribe link, a Drive public link (/l/<token>), or a Mirage interaction's token (itk_…, sent with the interaction, good for 15 minutes), or the token in the link to a file of a message Mirage sent to Discord, or a Marketing link's sealed token (a tracked click, the open pixel, unsubscribe and the preference centre, a double opt-in confirmation); or a Food recipe's public link (/r/<token>) or a household's calendar feed. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
password | string | Yes | 1–200 characters |
Response 200
{
grant: string
expiresAt: number
}GET /v1/hooks/photos/links/:token/photos
The link's photos (thumbnails), oldest first, in pages.
Auth: none
| Path parameter | Description |
|---|---|
:token | A secret from a link: an issue automation rule's incoming-webhook token (shown once when issued), a map's share link, a notification recipient's confirm and unsubscribe link, a Drive public link (/l/<token>), or a Mirage interaction's token (itk_…, sent with the interaction, good for 15 minutes), or the token in the link to a file of a message Mirage sent to Discord, or a Marketing link's sealed token (a tracked click, the open pixel, unsubscribe and the preference centre, a double opt-in confirmation); or a Food recipe's public link (/r/<token>) or a household's calendar feed. |
| Query parameter | Type | Required | Notes |
|---|---|---|---|
cursor | string | No | up to 2,000 characters |
Response 200
{
title: string
photos: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
name: string
takenAt: number
width?: number
height?: number
duration?: number
thumbUrl?: string
location?: {
lat: number
lon: number
}
}[]
cursor?: string
}GET /v1/hooks/photos/links/:token/photos/:photoId
One photo through the link: its preview (and video), or the original when downloads are on (download=1).
Auth: none
| Path parameter | Description |
|---|---|
:token | A secret from a link: an issue automation rule's incoming-webhook token (shown once when issued), a map's share link, a notification recipient's confirm and unsubscribe link, a Drive public link (/l/<token>), or a Mirage interaction's token (itk_…, sent with the interaction, good for 15 minutes), or the token in the link to a file of a message Mirage sent to Discord, or a Marketing link's sealed token (a tracked click, the open pixel, unsubscribe and the preference centre, a double opt-in confirmation); or a Food recipe's public link (/r/<token>) or a household's calendar feed. |
:photoId | Photo id: the photo's file in Drive (fil_…). |
| Query parameter | Type | Required | Notes |
|---|---|---|---|
download | "0" | "1" | No |
Response 200
{
name: string
previewUrl?: string
videoUrl?: string
motionUrl?: string
downloadUrl?: string
location?: {
lat: number
lon: number
}
}POST /v1/hooks/photos/links/:token/archives
A ZIP of everything in the link (when downloads are on).
Auth: none
| Path parameter | Description |
|---|---|
:token | A secret from a link: an issue automation rule's incoming-webhook token (shown once when issued), a map's share link, a notification recipient's confirm and unsubscribe link, a Drive public link (/l/<token>), or a Mirage interaction's token (itk_…, sent with the interaction, good for 15 minutes), or the token in the link to a file of a message Mirage sent to Discord, or a Marketing link's sealed token (a tracked click, the open pixel, unsubscribe and the preference centre, a double opt-in confirmation); or a Food recipe's public link (/r/<token>) or a household's calendar feed. |
Response 202
{
archive: {
archiveId: string
status: "queued" | "building" | "ready" | "failed"
name: string
files: number
bytes: number
url?: string
error?: string
}
}GET /v1/hooks/photos/links/:token/archives/:archiveId
A link's ZIP: its status, and a one-hour download link once ready. No authentication.
Auth: none
| Path parameter | Description |
|---|---|
:token | A secret from a link: an issue automation rule's incoming-webhook token (shown once when issued), a map's share link, a notification recipient's confirm and unsubscribe link, a Drive public link (/l/<token>), or a Mirage interaction's token (itk_…, sent with the interaction, good for 15 minutes), or the token in the link to a file of a message Mirage sent to Discord, or a Marketing link's sealed token (a tracked click, the open pixel, unsubscribe and the preference centre, a double opt-in confirmation); or a Food recipe's public link (/r/<token>) or a household's calendar feed. |
:archiveId | ZIP download id (zip_…). |
Response 200
{
archive: {
archiveId: string
status: "queued" | "building" | "ready" | "failed"
name: string
files: number
bytes: number
url?: string
error?: string
}
}GET /v1/me/photos/library
The library: its settings, storage (shared with Drive's personal space) and counts. Made on first use.
Auth: user access token or platform agent key
Response 200
{
library: {
driveId: string
settings: {
ai: {
faces: boolean
objects: boolean
memories: boolean
}
keepLocation: boolean
}
usage: {
bytes: number
files: number
limit: number
}
counts: {
photos: number
favourites: number
albums: number
archive: number
trash: number
}
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
PATCH /v1/me/photos/settings
Opt-in features (faces, objects and text, memories: nothing runs while off) and whether new links keep locations.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
ai | object | No | |
ai.faces | boolean | No | |
ai.objects | boolean | No | |
ai.memories | boolean | No | |
keepLocation | boolean | No |
Response 200
{
library: {
driveId: string
settings: {
ai: {
faces: boolean
objects: boolean
memories: boolean
}
keepLocation: boolean
}
usage: {
bytes: number
files: number
limit: number
}
counts: {
photos: number
favourites: number
albums: number
archive: number
trash: number
}
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/days
Photos per local day on the timeline (newest first), for the scrubber and the grid's layout.
Auth: user access token or platform agent key
Response 200
{
days: {
day: string
count: number
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/timeline
Timeline photos taken between from and to (ms), newest first.
Auth: user access token or platform agent key
| Query parameter | Type | Required | Notes |
|---|---|---|---|
cursor | string | No | up to 2,000 characters |
limit | integer | No | 1–500; coerced from a string |
from | integer | No | coerced from a string |
to | integer | No | coerced from a string |
Response 200
{
photos: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}[]
cursor?: string
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/lists/:list
Favourites, the archive or the trash, newest first.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:list | favourites, archive or trash. |
| Query parameter | Type | Required | Notes |
|---|---|---|---|
cursor | string | No | up to 2,000 characters |
limit | integer | No | 1–500; coerced from a string |
Response 200
{
photos: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}[]
cursor?: string
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/trash/empty
Deletes everything in the trash for good (the files from Drive too).
Auth: user access token or platform agent key
Response 200
{
deleted: number
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/photos/batch
Several photos by id (the map's pins, a selection), in the order asked.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
photoIds | string[] | Yes | 1–200 items; each 3–64 characters |
Response 200
{
photos: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/photos/state
Favourite, unfavourite, archive, unarchive, move to the trash, restore or delete forever (trashed photos only).
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
photoIds | string[] | Yes | 1–500 items; each 3–64 characters |
action | "favourite" | "unfavourite" | "archive" | "unarchive" | "trash" | "restore" | "delete" | Yes |
Response 200
{
changed: string[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/photos/:photoId
A photo's details: when (and from where that's known) and where it was taken, camera, size, description, albums, and one-hour links to show it (and a Live Photo's video). Logged as opened.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:photoId | Photo id: the photo's file in Drive (fil_…). |
Response 200
{
photo: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
description?: string
location?: {
lat: number
lon: number
altitude?: number
from: "file" | "edit" | "sidecar"
}
camera?: {
make?: string
model?: string
lens?: string
exposure?: string
fNumber?: number
iso?: number
focal?: number
}
takenFrom: "file" | "client" | "added" | "edit" | "sidecar"
local?: string
offset?: number
source: "mcp" | "agent" | "drive" | "edited" | "web" | "shortcut" | "takeout" | "picker"
addedAt: number
displayUrl?: string
motionUrl?: string
albums: {
albumId: string
title: string
}[]
versions: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
PATCH /v1/me/photos/photos/:photoId
Description, capture time (takenAt, ms; null goes back to the file's) and location (null removes it; "file" goes back to the file's).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:photoId | Photo id: the photo's file in Drive (fil_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
description | string | No | up to 2,000 characters; can be null |
favourite | boolean | No | |
takenAt | integer | No | can be null |
location | object | null | "file" | No |
Response 200
{
photo: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
description?: string
location?: {
lat: number
lon: number
altitude?: number
from: "file" | "edit" | "sidecar"
}
camera?: {
make?: string
model?: string
lens?: string
exposure?: string
fNumber?: number
iso?: number
focal?: number
}
takenFrom: "file" | "client" | "added" | "edit" | "sidecar"
local?: string
offset?: number
source: "mcp" | "agent" | "drive" | "edited" | "web" | "shortcut" | "takeout" | "picker"
addedAt: number
displayUrl?: string
motionUrl?: string
albums: {
albumId: string
title: string
}[]
versions: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/photos/:photoId/original
A one-hour link to the original (inline=1 to show it; motion=1 for a Live Photo's video).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:photoId | Photo id: the photo's file in Drive (fil_…). |
| Query parameter | Type | Required | Notes |
|---|---|---|---|
inline | "0" | "1" | No | |
motion | "0" | "1" | No |
Response 200
{
url: string
name: string
mime: string
size?: number
expiresIn: number
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
PUT /v1/me/photos/photos/:photoId/poster
A video's poster frame, made in the browser (JPEG or WebP, base64, up to 1 MB).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:photoId | Photo id: the photo's file in Drive (fil_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
image | string | Yes | 10–1,400,000 characters |
Response 200
{
photo: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/search
Search by when (from/to, ms), where (a box: south,west,north,east; or near=lat,lon with km), words in names and descriptions, type and favourites.
Auth: user access token or platform agent key
| Query parameter | Type | Required | Notes |
|---|---|---|---|
cursor | string | No | up to 2,000 characters |
limit | integer | No | 1–500; coerced from a string |
from | integer | No | coerced from a string |
to | integer | No | coerced from a string |
bbox | string | No | up to 100 characters |
near | string | No | up to 60 characters |
km | number | No | 0.1–5000; coerced from a string |
q | string | No | up to 200 characters |
type | "photo" | "video" | "live" | "raw" | No | |
favourite | "0" | "1" | No | |
archived | "0" | "1" | No |
Response 200
{
photos: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}[]
cursor?: string
total: number
truncated: boolean
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/map
Every located photo as a point (id, latitude, longitude, time), for the map.
Auth: user access token or platform agent key
Response 200
{
points: [string, number, number, number][]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/albums
Your albums, most recently changed first, with covers and how many links show each.
Auth: user access token or platform agent key
Response 200
{
albums: {
albumId: string
title: string
description?: string
count: number
coverId?: string
coverUrl?: string
sort: "oldest" | "newest" | "added"
createdAt: number
updatedAt: number
links: number
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/albums
Creates an album, optionally with photos.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | Yes | 1–200 characters |
description | string | No | up to 2,000 characters |
photoIds | string[] | No | up to 500 items; each 3–64 characters |
Response 201
{
album: {
albumId: string
title: string
description?: string
count: number
coverId?: string
coverUrl?: string
sort: "oldest" | "newest" | "added"
createdAt: number
updatedAt: number
links: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/albums/:albumId
An album and a page of its photos, in its sort order.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:albumId | Album id (alb_…). |
| Query parameter | Type | Required | Notes |
|---|---|---|---|
cursor | string | No | up to 2,000 characters |
limit | integer | No | 1–500; coerced from a string |
Response 200
{
album: {
albumId: string
title: string
description?: string
count: number
coverId?: string
coverUrl?: string
sort: "oldest" | "newest" | "added"
createdAt: number
updatedAt: number
links: number
}
photos: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}[]
cursor?: string
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
PATCH /v1/me/photos/albums/:albumId
Renames an album, changes its description, cover (a photo in it; null for the first) or order.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:albumId | Album id (alb_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | No | 1–200 characters |
description | string | No | up to 2,000 characters; can be null |
coverId | string | No | 3–64 characters; can be null |
sort | "oldest" | "newest" | "added" | No |
Response 200
{
album: {
albumId: string
title: string
description?: string
count: number
coverId?: string
coverUrl?: string
sort: "oldest" | "newest" | "added"
createdAt: number
updatedAt: number
links: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
DELETE /v1/me/photos/albums/:albumId
Deletes the album (its photos stay in the library) and turns off its links.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:albumId | Album id (alb_…). |
Response 204 with no body.
Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/albums/:albumId/photos
Adds photos to an album (up to 20,000 in one album). Ones already there stay.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:albumId | Album id (alb_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
photoIds | string[] | Yes | 1–500 items; each 3–64 characters |
Response 200
{
album: {
albumId: string
title: string
description?: string
count: number
coverId?: string
coverUrl?: string
sort: "oldest" | "newest" | "added"
createdAt: number
updatedAt: number
links: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/albums/:albumId/photos/remove
Takes photos out of an album; they stay in the library.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:albumId | Album id (alb_…). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
photoIds | string[] | Yes | 1–500 items; each 3–64 characters |
Response 200
{
album: {
albumId: string
title: string
description?: string
count: number
coverId?: string
coverUrl?: string
sort: "oldest" | "newest" | "added"
createdAt: number
updatedAt: number
links: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/links
Links anyone can open: albums and chosen photos.
Auth: user access token or platform agent key
Response 200
{
links: {
linkId: string
url: string
albumId?: string
photoCount?: number
title: string
keepLocation: boolean
download: boolean
expiresAt?: number
hasPassword: boolean
visits: number
lastVisitAt?: number
createdAt: number
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/links
A link to an album or to chosen photos: view only, locations removed unless keepLocation, optional expiry and password.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
albumId | string | No | 3–64 characters |
photoIds | string[] | No | 1–500 items; each 3–64 characters |
title | string | No | up to 200 characters |
keepLocation | boolean | No | |
download | boolean | No | |
expiresAt | integer | No | > 0 |
password | string | No | 4–200 characters |
Response 201
{
link: {
linkId: string
url: string
albumId?: string
photoCount?: number
title: string
keepLocation: boolean
download: boolean
expiresAt?: number
hasPassword: boolean
visits: number
lastVisitAt?: number
createdAt: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
PATCH /v1/me/photos/links/:linkId
Changes a link: password null removes it; rotate makes a new address (the old one stops working).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:linkId | Link id (ilk_… for issue links, irl_… for remote links, plk_… for Photos links: not the link's secret). |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | No | up to 200 characters |
keepLocation | boolean | No | |
download | boolean | No | |
expiresAt | integer | No | > 0; can be null |
password | string | No | 4–200 characters; can be null |
rotate | boolean | No |
Response 200
{
link: {
linkId: string
url: string
albumId?: string
photoCount?: number
title: string
keepLocation: boolean
download: boolean
expiresAt?: number
hasPassword: boolean
visits: number
lastVisitAt?: number
createdAt: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
DELETE /v1/me/photos/links/:linkId
Turns a link off: it stops working at once.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:linkId | Link id (ilk_… for issue links, irl_… for remote links, plk_… for Photos links: not the link's secret). |
Response 204 with no body.
Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/uploads/check
Which of these SHA-256 hashes are already in the library (skip uploading them).
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
hashes | string[] | Yes | 1–1,000 items; each matches ^[0-9a-f]{64}$ |
Response 200
{
existing: {
[key: string]: string
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
403 | This token can't do that (it needs photos:upload). |
POST /v1/me/photos/uploads
Starts an upload into the library: answers Drive's part links (PUT each part, then complete), or duplicate with the photo when sha256 is already there. singlePart (iPhone Shortcuts) sends the whole file to one link; its size may then be left out.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | 1–255 characters |
ext | string | No | up to 10 characters |
size | integer | No | ≥ 0 |
mime | string | No | up to 200 characters |
sha256 | string | No | matches ^[0-9a-f]{64}$ |
takenAt | integer | No | |
albumId | string | No | 3–64 characters |
pairWith | string | No | 3–64 characters |
singlePart | boolean | No | |
source | "web" | "shortcut" | "agent" | "edited" | No |
Response 201
{
duplicate: false
uploadId: string
itemId: string
partSize: number
partCount: number
urls: {
[key: number]: string
}
contentType: string
name: string
} | {
duplicate: true
photo: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
403 | This token can't do that (it needs photos:upload). |
POST /v1/me/photos/uploads/:uploadId/parts
Links for more parts of an upload (an hour each).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:uploadId | Upload id (dup_…), from starting the upload. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
partNumbers | integer[] | Yes | 1–100 items; each 1–10000 |
Response 200
{
urls: {
[key: number]: string
}
expiresIn: number
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
403 | This token can't do that (it needs photos:upload). |
GET /v1/me/photos/uploads/:uploadId
The parts already stored (resuming).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:uploadId | Upload id (dup_…), from starting the upload. |
Response 200
{
uploadId: string
name: string
size: number
partSize: number
partCount: number
parts: {
partNumber: number
size?: number
}[]
expiresAt: number
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
403 | This token can't do that (it needs photos:upload). |
POST /v1/me/photos/uploads/:uploadId/complete
Finishes an upload: the photo (details and previews follow in the background), or the one it duplicates.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:uploadId | Upload id (dup_…), from starting the upload. |
Response 200
{
photo: {
photoId: string
kind: "raw" | "live" | "video" | "photo"
state: "main" | "archive" | "trash" | "motion"
name: string
mime: string
size: number
takenAt: number
day: string
width?: number
height?: number
duration?: number
favourite: boolean
thumbUrl?: string
located: boolean
pairId?: string
processed: boolean
trashedAt?: number
}
duplicate: boolean
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
403 | This token can't do that (it needs photos:upload). |
DELETE /v1/me/photos/uploads/:uploadId
Abandons an upload and the parts it stored.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:uploadId | Upload id (dup_…), from starting the upload. |
Response 204 with no body.
Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
403 | This token can't do that (it needs photos:upload). |
GET /v1/me/photos/imports
Google Takeout imports, newest first.
Auth: user access token or platform agent key
Response 200
{
imports: {
importId: string
status: "queued" | "canceled" | "done" | "failed" | "uploading" | "running"
files: {
name: string
size: number
format: "zip" | "tgz"
from: "upload" | "drive"
uploaded: boolean
}[]
entries: number
processed: number
imported: number
duplicates: number
skipped: number
failed: number
albums: number
bytes: number
error?: string
problems: string[]
createdAt: number
startedAt?: number
finishedAt?: number
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/imports
A Takeout import: archives to upload (files: name and size of each .zip or .tgz; answers an upload per file) or archives already in Drive (driveItemIds, personal files). Start it once they're uploaded.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
files | object[] | No | 1–100 items |
files[].name | string | Yes | 1–255 characters |
files[].size | integer | Yes | 1–107374182400 |
driveItemIds | string[] | No | 1–100 items; each 3–64 characters |
Response 201
{
import: {
importId: string
status: "queued" | "canceled" | "done" | "failed" | "uploading" | "running"
files: {
name: string
size: number
format: "zip" | "tgz"
from: "upload" | "drive"
uploaded: boolean
}[]
entries: number
processed: number
imported: number
duplicates: number
skipped: number
failed: number
albums: number
bytes: number
error?: string
problems: string[]
createdAt: number
startedAt?: number
finishedAt?: number
}
uploads: {
index: number
partSize: number
partCount: number
urls?: {
[key: number]: string
}
parts?: {
partNumber: number
size?: number
}[]
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/imports/:importId
An import with the parts each of its uploads has stored (resuming).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:importId | Takeout import id (imp_…); under me/food a Food import (fim_…). |
Response 200
{
import: {
importId: string
status: "queued" | "canceled" | "done" | "failed" | "uploading" | "running"
files: {
name: string
size: number
format: "zip" | "tgz"
from: "upload" | "drive"
uploaded: boolean
}[]
entries: number
processed: number
imported: number
duplicates: number
skipped: number
failed: number
albums: number
bytes: number
error?: string
problems: string[]
createdAt: number
startedAt?: number
finishedAt?: number
}
uploads: {
index: number
partSize: number
partCount: number
urls?: {
[key: number]: string
}
parts?: {
partNumber: number
size?: number
}[]
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/imports/:importId/files/:index/parts
Links for the parts of one archive's upload (index: its place in files).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:importId | Takeout import id (imp_…); under me/food a Food import (fim_…). |
:index | The attachment's position in the message, from 0; or a Takeout archive's place in its import's files. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
partNumbers | integer[] | Yes | 1–100 items; each 1–10000 |
Response 200
{
urls: {
[key: number]: string
}
expiresIn: number
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/imports/:importId/start
Completes the archives' uploads and starts importing (in the background; re-imports skip what's there).
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:importId | Takeout import id (imp_…); under me/food a Food import (fim_…). |
Response 200
{
import: {
importId: string
status: "queued" | "canceled" | "done" | "failed" | "uploading" | "running"
files: {
name: string
size: number
format: "zip" | "tgz"
from: "upload" | "drive"
uploaded: boolean
}[]
entries: number
processed: number
imported: number
duplicates: number
skipped: number
failed: number
albums: number
bytes: number
error?: string
problems: string[]
createdAt: number
startedAt?: number
finishedAt?: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/imports/:importId/cancel
Stops an import; what it imported stays.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:importId | Takeout import id (imp_…); under me/food a Food import (fim_…). |
Response 200
{
import: {
importId: string
status: "queued" | "canceled" | "done" | "failed" | "uploading" | "running"
files: {
name: string
size: number
format: "zip" | "tgz"
from: "upload" | "drive"
uploaded: boolean
}[]
entries: number
processed: number
imported: number
duplicates: number
skipped: number
failed: number
albums: number
bytes: number
error?: string
problems: string[]
createdAt: number
startedAt?: number
finishedAt?: number
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
DELETE /v1/me/photos/imports/:importId
Removes a finished, failed or canceled import (and its uploaded archives). Imported photos stay.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:importId | Takeout import id (imp_…); under me/food a Food import (fim_…). |
Response 204 with no body.
Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/tokens
Personal tokens for uploading (iPhone Shortcut, Mac sync) and reading (MCP).
Auth: user access token or platform agent key
Response 200
{
tokens: {
tokenId: string
name: string
scopes: ("photos:upload" | "photos:read")[]
createdAt: number
lastUsedAt?: number
expiresAt?: number
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/tokens
Makes a token; its secret is in the answer only.
Auth: user access token or platform agent key
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | 1–100 characters |
scopes | ("photos:upload" | "photos:read")[] | Yes | at least 1 item |
expiresAt | integer | No | > 0 |
Response 201
{
token: {
tokenId: string
name: string
scopes: ("photos:upload" | "photos:read")[]
createdAt: number
lastUsedAt?: number
expiresAt?: number
}
secret: string
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
DELETE /v1/me/photos/tokens/:tokenId
Removes a photo token: it stops working at once.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:tokenId | Photo token id (pht_…), not the token itself. |
Response 204 with no body.
Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/exports
Full exports of the library: ZIPs of every original with an index of dates, places, albums and descriptions.
Auth: user access token or platform agent key
Response 200
{
exports: {
exportId: string
status: "queued" | "ready" | "failed"
files: number
bytes: number
createdAt: number
expiresAt: number
archives: {
archiveId: string
name: string
status: string
files: number
bytes: number
url?: string
}[]
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
POST /v1/me/photos/exports
Starts a full export of the library (originals as ZIPs of up to 10,000 files and 10 GB, with photos.json and albums.json). Ask for it until it's ready.
Auth: user access token or platform agent key
Response 202
{
export: {
exportId: string
status: "queued" | "ready" | "failed"
files: number
bytes: number
createdAt: number
expiresAt: number
archives: {
archiveId: string
name: string
status: string
files: number
bytes: number
url?: string
}[]
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/exports/:exportId
An export with each ZIP's status and its download link (a day) once ready.
Auth: user access token or platform agent key
| Path parameter | Description |
|---|---|
:exportId | An export's id (pex_… for Photos, oex_… for an organization). |
Response 200
{
export: {
exportId: string
status: "queued" | "ready" | "failed"
files: number
bytes: number
createdAt: number
expiresAt: number
archives: {
archiveId: string
name: string
status: string
files: number
bytes: number
url?: string
}[]
}
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/log
The owner's personal log: every change and access (theirs, their tokens', link visitors').
Auth: user access token or platform agent key
| Query parameter | Type | Required | Notes |
|---|---|---|---|
cursor | string | No | up to 2,000 characters |
Response 200
{
events: {
activityId: string
action: string
actorId: string
actorLabel?: string
at: number
detail: {
[key: string]: unknown
}
}[]
cursor?: string
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |
GET /v1/me/photos/access
Who can see what: links, tokens, and any of the library's files shared through Drive.
Auth: user access token or platform agent key
Response 200
{
links: {
linkId: string
url: string
albumId?: string
photoCount?: number
title: string
keepLocation: boolean
download: boolean
expiresAt?: number
hasPassword: boolean
visits: number
lastVisitAt?: number
createdAt: number
}[]
tokens: {
tokenId: string
name: string
scopes: ("photos:upload" | "photos:read")[]
createdAt: number
lastUsedAt?: number
expiresAt?: number
}[]
driveShares: {
itemId: string
name: string
principalId: string
role: string
}[]
driveLinks: {
itemId: string
name: string
}[]
}Errors
| Status | Message |
|---|---|
403 | Caity can look at your photos but not change them. |
403 | This token can't do that (it needs photos:read). |
403 | Photos are only for the person signed in, or their own photo token. |