Sharing and access
Permissions decide whether you may call an operation. They do not decide which rows come back. Two mechanisms do that, and they answer different questions:
- Row ACLs: who inside the workspace can reach this particular row.
- Share links: how something reaches someone who has no account at all.
Neither weakens the workspace boundary. Row-level security runs first and unconditionally; ACLs narrow within what it already allows.
Row ACLs
A row that can be shared carries an ACL: a map from subject to the operations that subject may perform.
Code
| Prefix | Subject |
|---|---|
u_ | One person, by user id. |
t_ | Everyone in the workspace, by workspace id. |
r_ | Everyone holding a role, by role id. |
| Op | Means |
|---|---|
r | Read it. |
w | Read and change it. |
o | Read, change, and decide who else can reach it. |
The operations nest: granting o stores ["r","w","o"], and granting w stores
["r","w"]. You do not have to send the implied operations, and reading them back returns
them expanded.
An ACL is optional, and it only narrows
Most rows carry no ACL, and that is the ordinary state rather than a gap: the row is governed by permissions alone, so anyone in the workspace holding the permission for an operation may perform it. Attaching an ACL is how you take one row out of that default and confine it to the people you name.
It only ever subtracts. Naming someone in an ACL does not hand them access their role and plan do not already carry. It narrows who, among those who could otherwise reach the row, still can. The two gates apply in order: the permission decides whether an operation is open to you at all, and the ACL decides whether this particular row is.
That is why null and {} both mean "no narrowing applied" rather than "nobody": an ACL
expresses a restriction, so an empty one restricts nothing. Clearing the map is how you
lift a restriction you no longer want, and to impose one you name at least one subject.
Set an ACL
Rows that support ACLs accept the map on create and update, and designs and infographics expose it on its own endpoint:
A malformed map is refused with 422 rather than partially applied: subject keys must be a
known prefix plus a uuid, and operations must come from r/w/o.
Where the filtering happens
An ACL is applied in the query rather than after it. A list endpoint over ACL-carrying
rows returns only the rows you can read; it does not return everything and mark the rest.
A row you cannot reach is therefore indistinguishable from a row that does not exist, and
a GET by id answers 404 rather than 403. That is deliberate, because a 403 would
confirm the row exists.
Conversations use members, not ACLs
Threads and spaces share through an explicit member list instead:
Code
Same idea, different shape, because a conversation has participants rather than readers.
Share links
A share link reaches people with no account. It is a token in a URL, served from the public portal, and it is the only way workspace content leaves the authenticated surface.
Links can carry an expiry, a password and caps on how much can be downloaded, and they
count views and downloads so you can see whether one is being used. Creating them requires
share-link:create, which a plan holds only with module-media-sharing.
Recipients use the unauthenticated endpoints under /public/share/{token}: verify the
password first if the link has one, then read or download. Upload links work the same way
in reverse, with /public/upload/{token} letting someone contribute files without an
account.
Treat share-link:create as more consequential than other permissions. Every other
permission on this platform decides what someone can do inside the workspace; this one
decides what leaves it. A revoked link stops working immediately (deleting the share link
is the revocation), but anything already downloaded cannot be recalled.
Choosing a mechanism
| You want | Use |
|---|---|
| Some people in the workspace should not see this row | A row ACL |
| Some people should read but not change it | An ACL with r |
| A client with no account needs the files | A share link |
| Someone outside needs to contribute files | An upload link |
| A conversation should include specific colleagues | Thread members |
Using a share link where an ACL would do publishes something that did not need publishing. Using an ACL where a share link is needed usually ends with someone emailing a zip file, which is worse.
The product guide covers the same subject from the app's side: the "Who can use this" control these ACLs sit behind, and when a share link is the right answer instead.
