Agents & developers
API documentation
Connect Claude, Cursor, OpenClaw, or your own agent to Zegazone collections and media. Look up single items with media.get / collections.get, search with media.search, or resolve share URLs with collections.get_by_slug. Schema version 2026-06-03.1.
Connect your AI agent
Two ways to connect — both expose identical tools. Choose based on your agent type.
Option 1 — Recommended
Remote MCP
No installation required. Works with any cloud-based AI agent.
Claude.ai Cloud
Add as a custom MCP connector in Claude.ai settings:
https://mcp.zegazone.com/mcp
Cursor (remote mode)
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"zegazone": {
"url": "https://mcp.zegazone.com/mcp"
}
}
}Complete browser OAuth when prompted. Done.
Option 2 — Developers
Local MCP
For agents running on your local machine. Best for developers who want offline access or are debugging the API.
1. Pair your account
npx @zegazone_mcp/mcp@latest --pair
2. Add to MCP config
{
"mcpServers": {
"zegazone": {
"command": "npx",
"args": ["@zegazone_mcp/mcp@latest"]
}
}
}Credentials stored locally in ~/.zegazone-mcp/credentials.json
Test your connection: Ask your agent: “Use Zegazone MCP to ping and tell me who I'm logged in as.”
Agent-driven UI
Your AI controls Zegazone
Agents do not only reply in chat — they can push results directly into your Zegazone player in real time. After browsing, searching, or filtering your library, an agent calls ui.state.set (MCP tool ui_state_set) to open collections, switch grid or carousel view, and select a specific media item. Changes broadcast to any tab where you have the player open.
“Show me the most viewed public collections this week”
Agent browses public collections sorted by views, pushes results to your player live
“Find public photography collections”
Agent searches for photography collections, displays them in your collections grid
“Show me my collections with no tags”
Agent scans your library, filters untagged ones, shows them so you can work through them
“Show me collections shared with me that I haven't opened”
Agent checks your shared collections, filters by not yet viewed, pushes the list
“Open my boating collection and select the video called Holiday 2025”
Agent navigates the player to exactly that media item instantly
# Open a collection and select media (REST)
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"ui.state.set","collection_id":123,"media_id":456,"view_mode":"grid","source":"agent"}'
# Push browse results into the collections grid (live in the player)
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"ui.state.set","browse_results":[123,456,789],"browse_title":"Most viewed this week","browse_source":"agent","source":"agent"}'Read current player context with ui.state.get / ui_state_get before navigating so the agent stays aligned with what you see.
OpenAPI 3.1
Machine-readable spec for tooling and codegen.
MCP server
Package @zegazone_mcp/mcp@latest — all 63 tools expose typed input schemas (field names, types, descriptions) plus lookup tools media_get, collections_get, and media_search.
REST entrypoint
POST https://api.zegaphone.com/functions/v1/thirdparty-v1
Local MCP installation
For Claude Desktop, Cursor local mode, and OpenClaw. Install with a single command — no repo clone required.
# One-time OAuth pairing (opens browser) npx @zegazone_mcp/mcp@latest --pair # Verify install (expect 2.0.5+) npx @zegazone_mcp/mcp@latest --version # After an API update: pin the new version in mcp.json, then reload MCP in your client
Your credentials are stored locally in ~/.zegazone-mcp/credentials.json and refresh automatically. Pin @zegazone_mcp/mcp@latest in your MCP config for typed tool schemas on every operation; use operations_list or schema_get to discover ops and fields.
Prefer remote MCP? If you're using Claude.ai Cloud or another cloud agent, use the remote MCP connector instead — no local installation needed.
Once connected, OpenClaw agents can control every aspect of Zegazone — creating collections, uploading media, managing sharing and collaborators, and controlling the UI — making it the most powerful way to use Zegazone with an AI agent.
# One-time OAuth pairing npx @zegazone_mcp/mcp@latest --pair # Add to OpenClaw config (pin version for named lookup tools) openclaw config set mcpServers.zegazone.command "npx" openclaw config set mcpServers.zegazone.args '["@zegazone_mcp/mcp@latest"]'
Or via OpenClaw settings JSON:
{
"mcpServers": {
"zegazone": {
"command": "npx",
"args": ["@zegazone_mcp/mcp@latest"]
}
}
}OpenClaw supports both stdio and HTTP/SSE MCP transports. Zegazone uses the stdio transport via npx.
Discover capabilities: call operations_list, then operation_describe or schema_get for field schemas.
Lookup tools: media_get, collections_get, collections_get_by_slug, media_search.
Typed schemas (2.0.5+): all 61 named tools expose field-level input schemas — pin @zegazone_mcp/mcp@latest so models receive typed parameters instead of a generic args blob.
After a Zegazone API release, update the pinned package in your MCP config and reload the MCP client so new named tools appear. Use thirdparty_call only when no dedicated tool exists yet.
Uploading files
MCP agents and REST clients can upload file bytes directly to Cloudflare R2 using a presigned PUT URL (15-minute expiry). This is separate from playback: stored objects are still served via token-based r2-proxy URLs in the app.
- Call media.get_upload_url (or collections.get_upload_url for cover images). MCP tools: media_get_upload_url, collections_get_upload_url.
- PUT your file to upload_url with the matching Content-Type header. No Authorization header on the PUT.
- Pass the returned r2_key as source_url in media.create, or as thumbnail_url in media.create / media.update / collections.update.
# 1) Presigned upload URL
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"media.get_upload_url","collection_id":123,"filename":"photo.jpg","content_type":"image/jpeg"}'
# 2) PUT file bytes (paste upload_url from step 1)
curl -X PUT -H 'Content-Type: image/jpeg' --data-binary @photo.jpg 'UPLOAD_URL_FROM_STEP_1'
# 3) Create media using r2_key from step 1
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"media.create","collection_id":123,"source_url":"r2://USER_ID/123/TIMESTAMP_photo.jpg","viewer":"image","name":"My Photo"}'What can agents do?
Example natural-language prompts you can give Claude, Cursor, or OpenClaw once MCP is connected:
- “What's the description on media item 1936?”
- “Find my media tagged 'holiday' across all collections”
- “Open the collection at charliefairbairn/boating and list what's in it”
- “Create a collection called 'Research 2026' and add these URLs to it”
- “Share my boating collection with tom22”
- “Show me the most viewed public collections this week”
Displays results live in your Zegazone player — not only in chat.
OAuth setup
- Register an OAuth client (or use the bundled MCP client) with PKCE (S256).
- Send the user to https://www.zegazone.com/?oauth=1&client_id=…&redirect_uri=…&code_challenge=… with scopes such as collections:read collections:write media:read media:write.
- Exchange the authorization code at https://api.zegaphone.com/functions/v1/thirdparty-oauth-token.
- Call thirdparty-v1 with Authorization: Bearer <access_token> and your Supabase apikey.
Lookup & search
Prefer single-item reads over listing entire collections. Via MCP, use the matching tool name (snake_case); via REST, send the op directly.
media_get→media.getYou know a media id and need its metadata or text note.
collections_get→collections.getYou know a collection id and need name, sharing, or stats.
collections_get_by_slug→collections.get_by_slugYou have a public URL handle + slug.
media_search→media.searchYou have a keyword but not an id (requires query).
operations_list→operations.listDiscover all ops and OAuth scopes.
schema_get→schema.getFull machine-readable contract for an op's fields.
REST examples
# Get one media item by id
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"media.get","media_id":1936}'
# Get one collection by id
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"collections.get","collection_id":123}'
# Search your media by keyword
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"media.search","query":"boating","limit":20}'
# Resolve a public share URL (handle + slug)
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"collections.get_by_slug","handle":"charliefairbairn","slug":"boating"}'
# List your collections
curl -sS -X POST 'https://api.zegaphone.com/functions/v1/thirdparty-v1' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"op":"collections.list","limit":20}'Operations (70)
All requests use POST with an op field. See OpenAPI for field-level schemas.
Meta
operation.describeno scopeReturn the schema.request entry for one operation name (from schema.get.operations).
operations.listno scopeList thirdparty-v1 operation names and required OAuth scopes (compact).
pingno scopeHealth/auth sanity check for the current access token.
schema.getno scopeReturns this API schema contract for tooling and bots.
Collections
collections.archivecollections:writeSet is_archived=true on an owned collection (hidden from default lists).
collections.batch.getcollections:readFetch up to 30 collections by id in one call (owned, non-deleted only). Order matches the request ids where possible.
collections.createcollections:writeCreate a new collection owned by the token subject. If thumbnail_url is http(s), the image is downloaded to R2; raster images are processed through the same GCF image pipeline as media uploads (WebP main + thumbnail on the media table, then the collection stores the resulting r2:// thumbnail). When parent_collection_id is set, creates a sub-collection (is_subcollection=true) and inserts the inline subcollection stub media row in the parent collection (requires media:write in addition to collections:write).
collections.deletecollections:deleteDelete an owned collection: soft (is_deleted) by default; hard removes the row (DB cascades media). Non-empty soft-delete requires cascade_media or hard mode.
collections.exportcollections:readExport one owned collection’s media metadata as JSON or CSV (no binary file bodies; use media.download per item for R2 content).
collections.getcollections:readFetch one collection by id when the token subject owns it or is an accepted collaborator.
collections.get_by_slugcollections:readResolve a collection from its public URL handle and slug (www.zegazone.com/{handle}/{slug}). Returns the row when the collection is public/registered or the caller has read access.
collections.get_upload_urlcollections:writeMint a presigned R2 PUT URL for a collection cover image only (purpose=cover). After PUT, set collections.update thumbnail_url to the returned r2_key. Do NOT use this for media item thumbnails — use media.get_upload_url with purpose=thumbnail, then media.thumbnail_finalize.
collections.listcollections:readList collections owned by the token subject.
collections.publishcollections:writePublish an owned collection (share_access_level=public, set share_slug).
collections.reordercollections:writeAssign collection position order by explicit ordered_ids (subset of owned non-deleted collections).
collections.restorecollections:writeRestore an owned soft-deleted collection (is_deleted = false).
collections.share_urlcollections:readGet the public share URL for a published collection (www.zegazone.com/{handle}/{slug}).
collections.unarchivecollections:writeSet is_archived=false on an owned collection.
collections.unpublishcollections:writeMake an owned collection private.
collections.updatecollections:writeUpdate one owned collection in place. Only provided fields are changed; optional thumbnail_url can be ingested into R2 like collections.create.
Media
media.archivemedia:writeSet is_archived=true on owned media.
media.batch.listmedia:readList media across multiple owned collections in one request (max 30 ids). Supports the same limit/offset as media.list on the combined result set.
media.copymedia:writeDuplicate one owned media row into another owned collection (same URLs; new row id).
media.createmedia:writeCreate a media row in an owned collection. viewer controls whether URL is treated as website link or ingested to R2. YouTube/Vimeo URLs are always stored as website links (external embed) even if viewer is video. Website items without thumbnail_url get OG image auto-fetched for carousel thumbnail.
media.deletemedia:deleteDelete owned media: hard (row delete, default, matches in-app) or soft (is_deleted). Batch via media_ids.
media.describemedia:readReturn full metadata for one readable media item, including resolved proxy URLs and inferred viewer information. Proxy URLs require Zegazone bearer auth — use media.download_url for a fetchable URL.
media.downloadmedia:readDownload private R2-backed media bytes with OAuth bearer auth.
media.download_urlmedia:readMint a short-lived proxied download URL on api.zegaphone.com for a readable media item. Token is embedded in the URL — no bearer auth required for fetch.
media.getmedia:readFetch one media row by id when the token subject can read its parent collection.
media.get_upload_urlmedia:writeMint a presigned R2 PUT URL for uploading file bytes directly (agents/MCP). After PUT with purpose=thumbnail, set thumbnail_url on the media row then call media.thumbnail_finalize to GCF-resize (200px thumb + 800px background). Do NOT use collections.get_upload_url for media thumbnails.
media.listmedia:readList media rows in accessible collections with optional filters.
media.movemedia:writeMove one or more owned media rows into another owned collection; optional start position.
media.reordermedia:writeRenumber positions 0..n-1 for all non-deleted media in one owned collection.
media.replacemedia:writeReplace the main content of an owned media item: same ingestion rules as media.create (website = external main_url; otherwise download to R2, raster images run the GCF pipeline). Optional name/type/thumbnail_url updates.
media.restoremedia:writeRestore owned soft-deleted media (is_deleted = false).
media.searchmedia:readSearch accessible media by name, description, and tags (ILIKE + tag overlap). Optional collection_id scopes to one collection.
media.text_note.addmedia:writeCreate an inline text-note media row (`text_note` body, `main_url` = inline://text-note). Same storage as app “text note” uploads; no R2 object. Rendering: set `display_mode` (plain|markdown|code) or `viewer` (text|markdown|code); `text` is an alias for plain. If both are sent, they must agree (plain↔text).
media.text_note.deletemedia:writeDelete one owned inline text-note media row (`main_url` = inline://text-note).
media.text_note.getmedia:readRead `text_note` and metadata for one inline text-note media row (`main_url` = inline://text-note). Caller must have read access to the parent collection.
media.text_note.updatemedia:writeUpdate `text_note` content (and optional display/name/description) for one owned inline text-note media row. Omitted `display_mode` and `viewer` leave type/viewer unchanged. Same display rules as `media.text_note.add`.
media.thumbnail_finalizemedia:writeAfter uploading raw image bytes via media.get_upload_url (purpose=thumbnail) and setting thumbnail_url on the media row, call this to GCF-resize to 200px WebP carousel thumb + 800px background in R2 and update thumbnail_url + background_image_url. Also runs automatically when media.create/update sets an r2:// thumbnail key.
media.thumbnail_uploadedmedia:writeDeprecated alias for media.thumbnail_finalize. After PUT bytes to media.get_upload_url (purpose=thumbnail), sets thumbnail_url if provided then runs GCF finalize (200px WebP thumb + 800px background). Prefer media.thumbnail_finalize.
media.unarchivemedia:writeSet is_archived=false on owned media.
media.updatemedia:writeUpdate fields on an existing owned media row. When thumbnail_url is set to an r2:// key from media.get_upload_url, the server auto-runs media.thumbnail_finalize (GCF 200px thumb + 800px background). After uploading via media.get_upload_url, you must call media.thumbnail_finalize if not setting thumbnail_url here. Do NOT use collections.get_upload_url for media thumbnails.
Discovery
collections.browseno scopeBrowse collections without a search query. Uses browse_collections_scoped for public/following/liked; own/shared via service-role queries. Scope own/shared need collections:read.
collections.searchno scopeSearch published collections (FTS + trigram). Proxies search_collections_scoped RPC (search-collections edge function). No extra OAuth scope; following/liked scopes need a user-bound token.
collections.stats.getcollections:readRead like/view stats for a collection the caller can access.
Social
aliases.followcollections:writeFollow another user publish alias (profile_aliases row).
aliases.following.listcollections:readList aliases and primary usernames the token subject follows.
aliases.listcollections:readList publish handles (primary username + profile_aliases) for the token subject.
aliases.unfollowcollections:writeUnfollow a publish alias.
collections.likecollections:writeLike a public collection (uses collection_like_set RPC, same as like-collection edge function).
collections.liked.listcollections:readList collections the token subject has liked.
collections.unlikecollections:writeRemove your like from a collection.
Collaboration
collaborators.invitecollections:writeInvite a Zegazone user to collaborate on a restricted/private owned collection by username.
collaborators.invite.acceptcollections:writeAccept a pending collaboration invite.
collaborators.invite.declinecollections:writeDecline a pending collaboration invite.
collaborators.invites.listcollections:readList pending collaboration invites sent by the token subject.
collaborators.invites.receivedcollections:readList pending collaboration invites received by the token subject.
collaborators.listcollections:readList accepted collaborators on an owned collection.
collaborators.revokecollections:writeRemove a collaborator or cancel a pending invite on an owned collection.
collaborators.update_permissionscollections:writeUpdate upload/edit/delete permissions for an existing collaborator or pending invite.
Profile
profile.getno scopeGet the token subject profile (username, display name, follower counts, bio, aliases).
profile.updateprofile:writeUpdate display_name (profiles.full_name) and/or bio (app_settings.about_you).
UI
ui.state.getcollections:read + media:readReturn the latest synchronized UI state for the token subject.
ui.state.setcollections:read + media:readPersist and broadcast the active UI selection, player chrome, and agent browse grid for the token subject.
API
bookmarks.createcollections:readSave a named bookmark of collection ids for later recall in the player.
bookmarks.deletecollections:readDelete a bookmark by id.
bookmarks.listcollections:readList collection browse bookmarks saved by the token subject.
mcp.revoke_sessionno scopeRevoke hosted MCP OAuth sessions for the authenticated user (D1 credentials + oauth_refresh_tokens). Accepts third-party access token or first-party Supabase session JWT from zegazone.com settings.