Skip to content

Sharing: resolve and claim share links and email invites #57

Open

Description

@Adron

Gap

The app can create and revoke share links but cannot consume one. A user handed a share link or an email invite has no path through the desktop app.

Endpoints

Method Path Purpose
GET /api/lists/shared/{token} resolve a share link for viewing
POST /api/lists/shared/{token} claim an edit/admin link as the signed-in user
GET /api/lists/shared/{token}/data token-authorized, read-only row data
GET/POST /api/lists/invite/{token} resolve / claim an email invite
GET/POST /api/documents/shared/{token} resolve / claim a document share link
GET/POST /api/documents/invite/{token} resolve / claim a document email invite

Acceptance criteria

  • A "Open a share link" entry point accepting a pasted URL or token.
  • Resolve shows what is being shared and at what role before claiming.
  • Claiming an edit/admin link adds it to the user's own lists/documents.
  • Read-only links render the row data without claiming.
  • Claiming is free for the invitee regardless of tier (documented) — do not gate it on customerStatus.

Activity

  1. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    ⚠️ The role vocabulary in this issue is wrong — the API does not accept Read-only / Edit / Admin

    I wrote these issues from /help/documents, which describes three permission levels as Read-only, Edit and Admin. Those are display names. The API rejects them. Verified live 2026-09-16 on a throwaway list:

    role=read-only     -> 400 {"error":"Invalid role. Must be watcher, collaborator, or manager"}
    role=edit          -> 400 {"error":"Invalid role. Must be watcher, collaborator, or manager"}
    role=admin         -> 400 {"error":"Invalid role. Must be watcher, collaborator, or manager"}
    
    role=watcher       -> 201 {"email":"…","role":"watcher","expiresAt":null,"url":"…/lists/invite/OG72S9Ee…"}
    role=collaborator  -> 201 {"email":"…","role":"collaborator","expiresAt":null,"url":"…"}
    role=manager       -> 201 {"email":"…","role":"manager","expiresAt":null,"url":"…"}
    

    So the wire values are watcher / collaborator / manager, and any UI needs an explicit mapping to the documented labels:

    Wire value Help-doc label Can
    watcher Read-only view content only
    collaborator Edit change content, but not rename, change public/private, or move
    manager Admin also rename, switch visibility, move, delete

    Don't send the display names, and don't show the wire values to users.

    Other live findings from #55/#56 (PRs #168/#169) that affect these issues

    The invite envelopes are asymmetric, which forces a read-after-write:

    POST   …/invites  -> 201 {email, role, expiresAt, url}     ← url, NO token
    GET    …/invites  -> 200 {invites:[{email, role, expiresAt, accepted, createdAt, token}]}  ← token, NO url
    DELETE …/invites/{token} -> 200 {"revoked":true}
    

    Revoke is keyed by token, which the create response doesn't return — so creating an invite must be followed by a GET to obtain a revocable handle. Copy-link rebuilds the URL from the token.

    role works despite the published OpenAPI request schema omitting it — that schema lists only email + expiresAt. Another case of the spec being thinner than reality; /help/api/sharing is right and the spec is wrong.

    Error shapes: invalid email → 400 "A valid email address is required"; unknown token → 404 "Invite not found or access denied" (note: not-found and not-permitted are deliberately indistinguishable).

    Ownership envelopes differ between domains — lists nest the object under data, documents under document; both carry userId, which neither ListSummary nor DocumentSummary models.

    customerStatus values are free / subscriber / subscriber:monthly / subscriber:annual — any non-free value grants access, so don't compare against the literal "subscriber".

    An undocumented notify:false on invite create is accepted (201) but not echoed back. It was deliberately not exposed in the UI because there's no way to confirm it actually suppresses the email — which is exactly what #58's "Email this person" toggle needs. Confirming that flag is the blocker for #58's notify toggle, and it needs a mailbox you control to verify.

    Still unverified for #57

    The claim/accept side (/api/{lists|documents}/invite/{token}) is cookie-session-only per the docs and needs a second verified account, so it is unimplemented. That's the substance of #57.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Deptharea:sharingArea: sharingparityWeb/API feature-parity work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions