Organizations ​
AYB supports hierarchical multi-tenancy with organizations, teams, members, and tenant assignments. Organization APIs are admin-token protected under /api/admin/orgs.
Use cases ​
Use organizations when one AYB deployment serves multiple business units or customers:
- B2B SaaS: one org per customer, teams per department, tenants per environment (
prod,staging,sandbox). - Enterprise internal platform: one org per division, shared users across teams, central tenant ownership for billing and audit.
- Agency model: one org per client account, multiple project teams, and explicit tenant assignment/unassignment during onboarding/offboarding.
Hierarchy and model ​
internal/tenant/org.go defines the core entities:
- Organization: top-level entity with
id,name,slug, optionalparentOrgId, andplanTier. - Team: child entity inside an org with
id,orgId,name, andslug. - OrgMembership: user membership in an org (
orgId,userId,role). - TeamMembership: user membership in a team (
teamId,userId,role).
Roles and permissions ​
AYB defines four org roles and two team roles as constants in internal/tenant/org.go.
Organization roles ​
owneradminmemberviewer
Team roles ​
leadmember
Effective permission mapping ​
internal/tenant/permissions.go maps org/team roles to effective tenant permission levels:
- Org
ownerandadminmap to tenant-level admin capability. - Org
memberandviewermap to tenant-level viewer capability. - Team
leadmaps to tenant-level member capability. - Team
membermaps to tenant-level viewer capability.
API inventory ​
The following endpoints are registered in internal/server/routes_admin_orgs.go.
Organizations ​
POST /api/admin/orgsGET /api/admin/orgsGET /api/admin/orgs/{orgId}PUT /api/admin/orgs/{orgId}DELETE /api/admin/orgs/{orgId}GET /api/admin/orgs/{orgId}/usageGET /api/admin/orgs/{orgId}/audit
Teams ​
POST /api/admin/orgs/{orgId}/teamsGET /api/admin/orgs/{orgId}/teamsGET /api/admin/orgs/{orgId}/teams/{teamId}PUT /api/admin/orgs/{orgId}/teams/{teamId}DELETE /api/admin/orgs/{orgId}/teams/{teamId}
Team members ​
POST /api/admin/orgs/{orgId}/teams/{teamId}/membersGET /api/admin/orgs/{orgId}/teams/{teamId}/membersPUT /api/admin/orgs/{orgId}/teams/{teamId}/members/{userId}/roleDELETE /api/admin/orgs/{orgId}/teams/{teamId}/members/{userId}
Organization members ​
POST /api/admin/orgs/{orgId}/membersGET /api/admin/orgs/{orgId}/membersPUT /api/admin/orgs/{orgId}/members/{userId}/roleDELETE /api/admin/orgs/{orgId}/members/{userId}
Tenant assignment ​
POST /api/admin/orgs/{orgId}/tenantsGET /api/admin/orgs/{orgId}/tenantsDELETE /api/admin/orgs/{orgId}/tenants/{tenantId}
Safety rules enforced by handlers ​
These constraints are enforced in org_handler.go, org_membership_handler.go, and team_membership_handler.go:
- Slug validation for organizations/teams (
tenant.IsValidSlug). - Parent org validation before create/update.
- Circular parent-org protection (
ErrCircularParentOrg). - Last-owner protection when removing or demoting org owners (
ErrLastOwner). - Team membership requires existing org membership first.
- Deleting an org requires
?confirm=trueand no assigned tenants.
SDK and curl examples ​
JavaScript admin workflow (fetch + admin token) ​
@allyourbase/js currently focuses on auth/data APIs and does not yet expose typed org-admin helpers. Organization admin routes are protected by the separate admin-token flow, so resolve an admin token from /api/admin/auth, then call org endpoints with fetch.
const baseURL = "http://localhost:8090";
const adminPassword = process.env.AYB_ADMIN_PASSWORD ?? "<admin-password>";
const adminAuth = await fetch(`${baseURL}/api/admin/auth`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: adminPassword }),
});
if (!adminAuth.ok) {
throw new Error(`Admin auth failed: ${adminAuth.status}`);
}
const { token: adminToken } = (await adminAuth.json()) as { token: string };
async function adminRequest<T>(path: string, init: RequestInit = {}): Promise<T> {
const response = await fetch(`${baseURL}${path}`, {
...init,
headers: {
Authorization: `Bearer ${adminToken}`,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
});
if (!response.ok) {
throw new Error(`Admin request failed: ${response.status}`);
}
return response.status === 204 ? (undefined as T) : ((await response.json()) as T);
}
const org = await adminRequest<{ id: string }>("/api/admin/orgs", {
method: "POST",
body: JSON.stringify({ name: "Acme", slug: "acme", planTier: "pro" }),
});
await adminRequest("/api/admin/orgs/" + org.id + "/members", {
method: "POST",
body: JSON.stringify({ userId: "00000000-0000-0000-0000-000000000001", role: "admin" }),
});curl: create org, add member, assign tenant ​
# Create org
curl -X POST http://localhost:8090/api/admin/orgs \
-H "Authorization: Bearer $AYB_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Acme","slug":"acme","planTier":"pro"}'
# Add org member
curl -X POST http://localhost:8090/api/admin/orgs/<orgId>/members \
-H "Authorization: Bearer $AYB_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId":"<user-uuid>","role":"owner"}'
# Assign tenant to org
curl -X POST http://localhost:8090/api/admin/orgs/<orgId>/tenants \
-H "Authorization: Bearer $AYB_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tenantId":"<tenant-uuid>"}'Dashboard ​
The Admin dashboard includes dedicated organizations and tenants views under the Admin sidebar section (see ui/src/components/layout-types.ts, Sidebar.tsx, and ContentRouter.tsx), in addition to API Explorer and direct admin API access.