URSY.PHOTOSPART OF THE URSY NETWORKSign inOpen library
URSY.PHOTOS

Photos API

Connect a website, script or agent to your private photo library through URSY ID. The website and API use the same library and storage allowance.

ID login and account creation · Typed OpenAPI · Capabilities · Developer manifest · Connected apps

Base URL: https://ursy.photos/api/v1. Resource: https://ursy.photos/api. Send an access token in the Authorization: Bearer header. Request only the permissions needed.

Pagination and synchronization

GET /photos?limit=50 returns next_cursor, library_revision and sync_cursor. Follow the opaque cursor with the same filters. The cursor fixes an upload high-water mark, so concurrent uploads cannot shift or duplicate the remaining pages. Existing records can change; use the changes feed after the listing. Photo cursors expire after one hour. The legacy after parameter remains supported. Albums expose next_after.

Poll GET /changes?cursor=... with the sync cursor. Changes and deletion tombstones are retained for 30 days. CURSOR_EXPIRED requires a fresh listing. A narrowed selection invalidates cursors for the previous selection.

Files and uploads

Create an upload with POST /upload/init and JSON name, size, mime. For a selected-album grant, include its album_id. Upload raw chunks to POST /upload/chunk?upload_id=...&offset=..., at most 2 MiB each. Resume with GET /upload/{upload_id}. Finish with JSON upload_id, sha256; only a verified receipt confirms storage. Sessions expire after 24 hours. An upload belongs to its initiating grant.

For bytes, request POST /photos/{id}/asset-grants with size: "md" or "original". URLs expire within five minutes and recheck the grant. Originals preserve the uploaded bytes and require both photos.originals.read and photos.location.read. Previews contain no embedded EXIF. Image limits are 25 MiB, 24 megapixels and 16,000 pixels per edge; check capabilities for the server’s current HEIC support.

Selected resources

During ID authorization, use authorization_details containing one ursy_photos_access object with album_ids and/or photo_ids. The same limits apply to lists, media, uploads, changes, sharing and exports.

Controlled sharing

POST /shares/plan prepares an exact photo selection and returns an ID approval_url. Show the plan to the owner and let them approve it at ID. Only then send plan_id, plan_hash to POST /shares/apply. A changed library or selection returns PLAN_STALE. Shared selections are frozen and contain previews only. Adding a photo to the source album does not silently publish it.

Export tasks

POST /exports queues a ZIP containing unchanged originals, metadata, album memberships and checksums. Supply an Idempotency-Key or JSON operation_id. Optional selection fields narrow the export. Poll GET /tasks/{id} and cancel using DELETE /tasks/{id}. Completed tasks offer POST /tasks/{id}/download-grant for a five-minute download URL. Task records and artifacts expire after 24 hours. Permission or library changes stop an export or invalidate its artifact; create a fresh export.

MCP

POST JSON-RPC to https://ursy.photos/api/mcp. server/discover publishes current versions and the Tasks extension. The current protocol uses per-request _meta and matching Mcp-Method/Mcp-Name headers. Clients declaring io.modelcontextprotocol/tasks get durable export task handles and poll tasks/get; tasks/cancel requests cancellation. Existing 2025 clients can use initialization and ordinary task tools. Notifications and subscriptions are not offered.

Operations

GET /photos
List authorised photos with a stable upload snapshot and opaque pagination cursor. Scopes: photos.read
GET /photos/search
Search metadata, date, album, favourites, format, camera or filename. Location filtering also requires location permission. Scopes: photos.read
GET /photos/{id}
photos get Scopes: photos.read
GET /quota
photos quota Scopes: photos.read
GET /me
account get Scopes: photos.read
PATCH /photos/{id}
photos favourite Scopes: photos.metadata.write
POST /photos/{id}/asset-grants
Request a short-lived asset URL; originals additionally require original and location permissions. Scopes: photos.read
GET /albums
albums list Scopes: photos.read
POST /albums
albums create Scopes: photos.albums.write
GET /albums/{id}
albums get Scopes: photos.read
PATCH /albums/{id}
albums rename Scopes: photos.albums.write
DELETE /albums/{id}
Delete an album; retain its photos and revoke its album sharing links. Scopes: photos.albums.write
POST /albums/{id}/photos
albums add photos Scopes: photos.albums.write
DELETE /albums/{id}/photos
albums remove photos Scopes: photos.albums.write
GET /albums/{id}/photos
albums photos Scopes: photos.read
POST /upload/init
uploads init Scopes: photos.upload
POST /upload/finish
uploads finish Scopes: photos.upload
GET /upload/{upload_id}
uploads status Scopes: photos.upload
DELETE /upload/{upload_id}
uploads cancel Scopes: photos.upload
POST /upload/chunk
Upload raw bytes, at most 2 MiB, at the confirmed contiguous offset. Scopes: photos.upload
DELETE /photo/{id}
photos trash Scopes: photos.trash.write
POST /photo/{id}/restore
photos restore Scopes: photos.trash.write
GET /trash
trash list Scopes: photos.read
GET /timeline
timeline Scopes: photos.read
GET /stack
stack Scopes: photos.read
GET /changes
library changes Scopes: photos.read
GET /activity
library activity Scopes: photos.activity.read
GET /duplicates
photos duplicates Scopes: photos.read
GET /library/status
library status Scopes: photos.read
GET /shares
shares list Scopes: photos.shares.read
POST /shares/plan
shares plan Scopes: photos.shares.plan
GET /shares/plans/{id}
shares get plan Scopes: photos.shares.plan
POST /shares/apply
Publish only the unchanged exact plan after the owner approves it on URSY ID. Scopes: photos.shares.apply
DELETE /shares/{id}
shares revoke Scopes: photos.shares.plan
POST /shares
shares legacy plan Scopes: photos.shares.plan
POST /exports
Queue an export with unchanged originals, metadata, album membership and SHA-256 checksums. Scopes: photos.export, photos.originals.read, photos.location.read
GET /tasks
tasks list Scopes: photos.export
GET /tasks/{id}
tasks get Scopes: photos.export
DELETE /tasks/{id}
tasks cancel Scopes: photos.export
POST /tasks/{id}/download-grant
exports download grant Scopes: photos.export, photos.originals.read, photos.location.read

Errors carry error.code, a message and a request ID. The legacy code, message, data.status fields remain available. Refresh authentication after a 401, request the missing permissions after a 403, and follow Retry-After on a 429. Private results never contain sponsorship. Paid checkout, Family accounts, semantic search and automatic camera-roll backup are outside this website release.