The seam
app/api/vendo/[...vendo]/route.ts
Membership
Return
[] for a caller who belongs to nothing. Leave the seam off entirely and
nothing is asserted: sharing degenerates to plain ownership, no org owns a
workspace, and there are no org pools to count.
…or let Cloud keep the directory
Writing and maintaining this query is real work. Tenants is Vendo Cloud keeping that roster for you — withVENDO_API_KEY set and no
auth.memberships of your own, the SDK asks the directory instead. Set
auth.memberships and your assertion wins, always; the directory is never
consulted.
Asserted, never stored
This is the whole model, and the rules that fall out of it:- Resolved once per request and read by every door downstream, so one request can never see two different answers.
- Never persisted. There is no orgs table and no members table in the store. Removing someone from an org in your tables removes them from Vendo on their very next request — nothing to propagate, and no cache to invalidate when you assert memberships yourself.
- An ephemeral visitor is not even asked. A principal you marked
ephemeral: truebelongs to no org by construction — your host issued them nothing. - Your resolver mints user principals only. Org context is derived from
membership, so a resolver returning
kind: "org", or a subject in the reservedvendo:namespace, is rejected at the wire.
Because the answer is asserted per request, your identity system stays the
single source of truth for who is in what. Vendo never disagrees with it,
because Vendo never has a second copy.
One name, everywhere
An org is spelled the same way wherever it appears. That string is the grant principal and it is the limits pool.
A grant is matched against the memberships this request asserted, so one row
covers the whole org and never needs a per-user fan-out.
What a membership unlocks
Four things, all out of that one array.Sharing
A grant whose principal is
org:<org> or team:<org>/<id> matches every
member you asserted. Share an app once, the org has it.org:acmeThe org workspace
Paths under
/orgs/<org>/** are owned by the org, not the person who made
them. An admin member is an implicit owner of every app it holds./orgs/acme/apps/…Org policy
Guard rules that ride on top of your deployment’s own policy for the members
of one org.
vendo/org-policy@1Limits
Every asserted org is automatically a shared meter named
org:<org>, so one
line caps an org’s spend.count(action, { pool })Verify it
vendo doctor reads files on disk, so it cannot see this seam. Run your app and
make one real call as a member of an org.
An org-shared app that a member cannot open means the seam returned no membership
for them — log what your callback returned for that subject and compare the org
string against the grant’s.
Where to go next
Auth
The preset this seam rides, and the principal it is keyed on.
auth: authJs()Limits
The org cap that needs nothing but the seam above.
pool: "org:acme"Handler options
Where
memberships, facts, and pools sit among every other key.createVendo