GenuineAIGenuineAI
For usersFor developers
  • Overview
  • Guides
  • How-to
  • API reference
Documentation
  • Quickstart
  • Authentication
  • Errors
  • Rate limits
  • API reference
Platform
  • Platform overview
  • Solutions
  • Pricing
  • Sign in
Company
  • Status
  • Trust Center
  • Contact
  • Terms of Service
  • Privacy Policy

© 2026 GenuineAI Ventures LLC

support@genuinehq.com
Start here
QuickstartAuthentication
Working with the API
Objects and fieldsErrorsPaginationRate limitsMaintenance windowsVersioning and deprecation
Security
Security and tenancyRoles and permissionsPermission referenceSharing and access
Your plan
Plans and modules
Guides

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
{ "u_3f2a9c1e-7b44-4c0d-9d2f-8a1b6e5c0d31": ["r", "w", "o"], "r_8c14e45f-1b22-4a10-9e3d-2f6c1a4b7e90": ["r"] }
PrefixSubject
u_One person, by user id.
t_Everyone in the workspace, by workspace id.
r_Everyone holding a role, by role id.
OpMeans
rRead it.
wRead and change it.
oRead, 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
POST /api/v1/threads/{id}/members share with someone GET /api/v1/threads/{id}/members see who it is shared with DELETE /api/v1/threads/{id}/members/{userId}

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 wantUse
Some people in the workspace should not see this rowA row ACL
Some people should read but not change itAn ACL with r
A client with no account needs the filesA share link
Someone outside needs to contribute filesAn upload link
A conversation should include specific colleaguesThread 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.

Last modified on October 8, 2026
Permission referencePlans and modules
On this page
  • Row ACLs
    • An ACL is optional, and it only narrows
    • Set an ACL
    • Where the filtering happens
    • Conversations use members, not ACLs
  • Share links
  • Choosing a mechanism
JSON
curl -X PUT "https://api.genuineai.app/api/v1/designs/{id}/acl" \ -H "X-Api-Key: gai_…" \ -H "X-Tenant-Id: <workspace-id>" \ -H "Content-Type: application/json" \ -d '{"acl": {"r_8c14e45f-1b22-4a10-9e3d-2f6c1a4b7e90": ["r"]}}'
curl -X POST https://api.genuineai.app/api/v1/share-links \ -H "X-Api-Key: gai_…" \ -H "X-Tenant-Id: <workspace-id>" \ -H "Content-Type: application/json" \ -d '{ "share_type": "download", "name": "Q3 campaign assets", "file_ids": ["…"], "expires_at": "2026-09-30T00:00:00Z" }'