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 anall_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.
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.
/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.