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.

  1. Call media.get_upload_url (or collections.get_upload_url for cover images). MCP tools: media_get_upload_url, collections_get_upload_url.
  2. PUT your file to upload_url with the matching Content-Type header. No Authorization header on the PUT.
  3. 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

  1. Register an OAuth client (or use the bundled MCP client) with PKCE (S256).
  2. 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.
  3. Exchange the authorization code at https://api.zegaphone.com/functions/v1/thirdparty-oauth-token.
  4. 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_getmedia.get

    You know a media id and need its metadata or text note.

  • collections_getcollections.get

    You know a collection id and need name, sharing, or stats.

  • collections_get_by_slugcollections.get_by_slug

    You have a public URL handle + slug.

  • media_searchmedia.search

    You have a keyword but not an id (requires query).

  • operations_listoperations.list

    Discover all ops and OAuth scopes.

  • schema_getschema.get

    Full 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 scope

    Return the schema.request entry for one operation name (from schema.get.operations).

  • operations.listno scope

    List thirdparty-v1 operation names and required OAuth scopes (compact).

  • pingno scope

    Health/auth sanity check for the current access token.

  • schema.getno scope

    Returns this API schema contract for tooling and bots.

Collections

  • collections.archivecollections:write

    Set is_archived=true on an owned collection (hidden from default lists).

  • collections.batch.getcollections:read

    Fetch up to 30 collections by id in one call (owned, non-deleted only). Order matches the request ids where possible.

  • collections.createcollections:write

    Create 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:delete

    Delete 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:read

    Export one owned collection’s media metadata as JSON or CSV (no binary file bodies; use media.download per item for R2 content).

  • collections.getcollections:read

    Fetch one collection by id when the token subject owns it or is an accepted collaborator.

  • collections.get_by_slugcollections:read

    Resolve 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:write

    Mint 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:read

    List collections owned by the token subject.

  • collections.publishcollections:write

    Publish an owned collection (share_access_level=public, set share_slug).

  • collections.reordercollections:write

    Assign collection position order by explicit ordered_ids (subset of owned non-deleted collections).

  • collections.restorecollections:write

    Restore an owned soft-deleted collection (is_deleted = false).

  • collections.share_urlcollections:read

    Get the public share URL for a published collection (www.zegazone.com/{handle}/{slug}).

  • collections.unarchivecollections:write

    Set is_archived=false on an owned collection.

  • collections.unpublishcollections:write

    Make an owned collection private.

  • collections.updatecollections:write

    Update one owned collection in place. Only provided fields are changed; optional thumbnail_url can be ingested into R2 like collections.create.

Media

  • media.archivemedia:write

    Set is_archived=true on owned media.

  • media.batch.listmedia:read

    List 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:write

    Duplicate one owned media row into another owned collection (same URLs; new row id).

  • media.createmedia:write

    Create 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:delete

    Delete owned media: hard (row delete, default, matches in-app) or soft (is_deleted). Batch via media_ids.

  • media.describemedia:read

    Return 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:read

    Download private R2-backed media bytes with OAuth bearer auth.

  • media.download_urlmedia:read

    Mint 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:read

    Fetch one media row by id when the token subject can read its parent collection.

  • media.get_upload_urlmedia:write

    Mint 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:read

    List media rows in accessible collections with optional filters.

  • media.movemedia:write

    Move one or more owned media rows into another owned collection; optional start position.

  • media.reordermedia:write

    Renumber positions 0..n-1 for all non-deleted media in one owned collection.

  • media.replacemedia:write

    Replace 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:write

    Restore owned soft-deleted media (is_deleted = false).

  • media.searchmedia:read

    Search accessible media by name, description, and tags (ILIKE + tag overlap). Optional collection_id scopes to one collection.

  • media.text_note.addmedia:write

    Create 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:write

    Delete one owned inline text-note media row (`main_url` = inline://text-note).

  • media.text_note.getmedia:read

    Read `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:write

    Update `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:write

    After 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:write

    Deprecated 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:write

    Set is_archived=false on owned media.

  • media.updatemedia:write

    Update 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 scope

    Browse 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 scope

    Search 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:read

    Read like/view stats for a collection the caller can access.

Social

  • aliases.followcollections:write

    Follow another user publish alias (profile_aliases row).

  • aliases.following.listcollections:read

    List aliases and primary usernames the token subject follows.

  • aliases.listcollections:read

    List publish handles (primary username + profile_aliases) for the token subject.

  • aliases.unfollowcollections:write

    Unfollow a publish alias.

  • collections.likecollections:write

    Like a public collection (uses collection_like_set RPC, same as like-collection edge function).

  • collections.liked.listcollections:read

    List collections the token subject has liked.

  • collections.unlikecollections:write

    Remove your like from a collection.

Collaboration

  • collaborators.invitecollections:write

    Invite a Zegazone user to collaborate on a restricted/private owned collection by username.

  • collaborators.invite.acceptcollections:write

    Accept a pending collaboration invite.

  • collaborators.invite.declinecollections:write

    Decline a pending collaboration invite.

  • collaborators.invites.listcollections:read

    List pending collaboration invites sent by the token subject.

  • collaborators.invites.receivedcollections:read

    List pending collaboration invites received by the token subject.

  • collaborators.listcollections:read

    List accepted collaborators on an owned collection.

  • collaborators.revokecollections:write

    Remove a collaborator or cancel a pending invite on an owned collection.

  • collaborators.update_permissionscollections:write

    Update upload/edit/delete permissions for an existing collaborator or pending invite.

Profile

  • profile.getno scope

    Get the token subject profile (username, display name, follower counts, bio, aliases).

  • profile.updateprofile:write

    Update display_name (profiles.full_name) and/or bio (app_settings.about_you).

UI

  • ui.state.getcollections:read + media:read

    Return the latest synchronized UI state for the token subject.

  • ui.state.setcollections:read + media:read

    Persist and broadcast the active UI selection, player chrome, and agent browse grid for the token subject.

API

  • bookmarks.createcollections:read

    Save a named bookmark of collection ids for later recall in the player.

  • bookmarks.deletecollections:read

    Delete a bookmark by id.

  • bookmarks.listcollections:read

    List collection browse bookmarks saved by the token subject.

  • mcp.revoke_sessionno scope

    Revoke 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.