Skip to main content
When someone shares a library with you, you can access it alongside libraries you own. Each library has one owner. Its LibraryResponse includes owner (an ID and optional display name) and your role (owner, viewer, or collaborator). The existing user_id remains the owner’s ID. Your role describes membership; your credential can further limit your access.

Role permissions

Your active library role sets these content and management limits: Deleting an album, face, or person is an organizational edit a collaborator can make; permanently deleting photos or a library is owner-only. Your credential must also allow the action and library. For example, deleting or reverting a photo version still requires the delete_permanently credential action, even when your role allows the operation. Invitation and membership operations also require a first-party session, as described below.

Credentials and scope

Content operations apply your current library role and your credential’s action and library scope. With an all_libraries API key or OAuth grant, you can reach every library currently accessible to your account, including ones you join after creating the credential. This also expands older credentials that previously reached only libraries you owned. A selected_libraries credential stays fixed to its selected IDs; joining a library does not add it to that set. You lose access if your membership is revoked or the owner’s account or library becomes unavailable. Review your existing all_libraries integrations when joining a library. Use a selected-library credential when an integration should keep a fixed scope. You still need to be the owner for owner-only actions.
You need a first-party Gumnut session to create, list, retrieve, or disable invitations; preview or join an invitation; manage members; or leave a library. You cannot call these operations with an API key or delegated OAuth credential. The generated Python and TypeScript SDK methods describe the HTTP contract, but configuring an SDK client with an API key does not grant you access.

Selecting a library

GET /api/libraries returns your owned and joined libraries as an unpaginated array. Each row identifies the owner and your current role without disclosing the owner’s private account usage. Once you have more than one live library, content requests that omit library_id fail with 400 and library_id required because user has multiple libraries. Pass an explicit library ID in SDK, MCP, and other multi-library clients. Joining a library does not automatically select it on your device.

Membership and invitations

As an owner, manage memberships at /api/libraries/{library_id}/members. GET lists non-owner memberships, with optional state, limit, and starting_after_id. Use PATCH on /{user_id} to change an active member’s role or DELETE to remove them. If you are a member, leave through POST /api/libraries/{library_id}/leave. The library response represents the owner without a synthetic membership row. Membership responses include the member’s public summary, role, state (active, left, or removed), and timestamps. If you remove someone, an older invitation cannot readmit them; leaving does not impose that cutoff. Invitation operations live under /api/libraries/invitations, not under /api/invitations or an individual library’s path. Each v1 invitation targets one library. As the owner, create one by posting library_id, role (viewer or collaborator), an absolute expires_at, and max_joiners. If you post an identical active policy, you get its existing link (200); a new policy returns 201. A matching inactive policy returns 409 invitation_inactive without a link. You can list invitations across your owned libraries or filter with library_id; the list contains metadata and usage, never secrets. Use GET /{invitation_id}/link to retrieve an active link, or POST /{invitation_id}/disable to stop new admissions without removing members.
You may be unable to issue invitations or join while sharing admission is disabled for rollout. A route appearing in the API reference or an SDK does not mean admission is enabled for your account.
Your browser link has the form /invite/{invitation_id}#secret=<secret> on the Gumnut app origin. Send its secret to the API only in the JSON body of POST /api/libraries/invitations/{invitation_id}/preview or /join; keep the link and secret out of logs and analytics. Preview returns limited library and owner attribution plus your eligibility without reserving a place. Join rechecks eligibility and commits your membership. Repeating a successful join does not consume another place or replace your existing role. Link retrieval and creation responses contain the secret-bearing URL; listing, preview, join, and ordinary library responses do not. Member and invitation lists return { data: [...], has_more: boolean } in newest-first order. limit defaults to 20 and accepts 1–200. Pass the last row’s membership ID or invitation ID as starting_after_id to get the next page; do not use a member’s user ID as a membership cursor. Sharing domain errors use { "detail": "...", "code": "..." }. For example, join can return 409 invitation_expired, invitation_disabled, invitation_exhausted, or new_invitation_required. Preview and join return the same generic 404 invitation_unavailable for invalid secrets, missing invitations, and unavailable libraries. Authentication, permission, rate-limit, and request validation failures retain their usual 401, 403, 429, and 422 shapes.

Storage and contribution

When you upload photos to a joined library, they count against its owner and library cap rather than your own allowance. A capacity failure does not reveal the owner’s private account usage. Newly stored photos record you as the contributor independently of the owner. A duplicate upload keeps the original photo’s contributor; historical attribution can be unknown. Your contributions remain in the library after you leave or are removed.