Organizations¶
Organizations represent companies, departments, or other entities that users can be affiliated with. They support hierarchical structures (parent/child), role-based membership, and domain-based association.
Data Model¶
Organization¶
| Field | Type | Description |
|---|---|---|
id |
uuid | Primary key |
name |
varchar(255) | Unique name (within active orgs) |
org_type |
varchar(30) | Organization type: company, department, division, team, or cost_center (default company) |
address |
text | Physical or mailing address |
website |
varchar(500) | Web URL |
domain |
varchar(255) | Email domain (e.g. acme.com) for auto-association |
parent_id |
uuid | FK to parent organization (nullable) |
contacts |
jsonb | Array of contact objects |
created_by |
uuid | FK to user who created the org |
deleted_at |
timestamp | Soft-delete sentinel (1970-01-01 = active) |
created_at |
timestamp | Creation timestamp |
updated_at |
timestamp | Last update timestamp |
Organization Membership¶
| Field | Type | Description |
|---|---|---|
id |
uuid | Primary key |
organization_id |
uuid | FK to organization |
user_id |
uuid | FK to user |
role |
varchar(50) | member, signatory, or approver |
added_by |
uuid | FK to user who added this member |
created_at |
timestamp | When the membership was created |
A unique constraint on (organization_id, user_id, role) allows a user to hold multiple distinct roles in the same organization.
Contact Object (JSON)¶
{
"name": "Jane Doe",
"email": "jane@acme.com",
"phone": "+1-555-0100",
"title": "Chief Technology Officer"
}
All fields except name are nullable.
Membership Roles¶
| Role | Description |
|---|---|
member |
User whose primary affiliation is with this organization. The org typically controls their email domain and account. |
signatory |
User authorized to approve actions or sign documents on behalf of the organization. |
approver |
User authorized to approve administrative actions, such as authorizing other members to join projects or groups. |
These roles are extensible -- new values can be added without schema changes since the column is a plain varchar.
Hierarchy¶
Organizations support arbitrary parent-child hierarchies via the parent_id self-reference. The system prevents circular references by walking the parent chain before accepting a new parent_id value.
API Endpoints¶
All endpoints are under /api/organizations. Authentication is required for all routes.
List Organizations¶
GET /api/organizations?page=1&pageSize=20&search=acme&sortBy=name&sortOrder=asc&includeDeleted=false
Permission: org:read
Returns a paginated list of organizations with member counts and parent org names.
Create Organization¶
POST /api/organizations
Content-Type: application/json
{
"name": "Acme Corp",
"domain": "acme.com",
"website": "https://acme.com",
"address": "123 Main Street, Springfield",
"parentId": null,
"contacts": [
{ "name": "Jane Doe", "email": "jane@acme.com", "phone": null, "title": "CTO" }
]
}
Permission: org:manage
Returns 201 with the full organization detail including members and children.
Get Organization¶
Permission: org:read
Returns the organization with its members, child organizations, and parent name.
Update Organization¶
PUT /api/organizations/:id
Content-Type: application/json
{
"name": "Acme Inc",
"domain": "acme.com",
"website": "https://acme.com",
"contacts": [...]
}
Permission: org:manage
All fields are optional. Returns 409 if a name conflict exists.
Delete Organization (Soft)¶
Permission: org:manage
Soft-deletes the organization. Returns 200 with a confirmation message.
Restore Organization¶
Permission: org:manage
Restores a soft-deleted organization.
Add Member¶
POST /api/organizations/:id/members
Content-Type: application/json
{
"userId": "uuid-of-user",
"role": "signatory"
}
Permission: org:manage
Assigns a role to a user. A user can hold multiple roles in the same organization. Returns 409 if the exact user+role combination already exists.
Remove Member Role¶
Permission: org:manage
Removes a specific role assignment from a user.
List Child Organizations¶
Permission: org:read
Returns an array of { id, name } for direct children.
Permissions¶
| Permission | Description | Default Roles |
|---|---|---|
org:read |
View organizations and memberships | admin, approver, resource_manager, requestor |
org:manage |
Create, edit, delete organizations and manage members | admin |
Audit Events¶
The following actions are logged in the audit trail:
organization.createdorganization.updatedorganization.soft_deletedorganization.restoredorganization.permanently_deletedorganization.member_added(includesuserIdandrolein metadata)organization.member_removed(includesuserIdandrolein metadata)
Frontend¶
The Organizations feature is accessible from the sidebar (requires org:read permission) and provides:
- Organization List (
/organizations): Paginated table with name, domain, parent, member count, and CRUD actions. - Organization Detail (
/organizations/:id): Full view with contact cards, child organization tags, and a members table with role badges. Supports adding/removing members with role selection.
Migration¶
Migration 020_organizations.ts creates the organization and organization_membership tables, the v_organization soft-delete view, and indexes for name uniqueness, domain lookups, and parent traversal.
Organization Types¶
The org_type discriminator allows the hierarchy to represent different organizational structures:
| Type | Description |
|---|---|
company |
Top-level legal entity (default) |
department |
Functional unit within a company |
division |
Business division or line of business |
team |
Working team or squad |
cost_center |
Budget or cost allocation center |
Types are extensible -- new values can be added to the ORG_TYPES constant in @floh/shared.
ReBAC Integration¶
Organization membership relationships are queryable through the RelationshipResolver:
check(user:X, member, org:Y)-- is X a member of Y (any role)?check(user:X, approver, org:Y)-- does X have theapproverrole in Y or any ancestor org?check(user:X, signatory, org:Y)-- same as above forsignatoryrole
Role checks for approver and signatory traverse the organization hierarchy upward, so a user with an approver role at the parent company level also satisfies the check for child org units.
Approval Integration¶
Workflows can reference organization roles for approval steps using the org-role: prefix:
This creates an approval that any user with the specified role in the specified organization (or its ancestors) can fulfill.
Future Extensibility¶
- New membership roles: Add values like
billing,technical_contact, orexecutivewithout schema changes. - Domain-based auto-association: The
OrganizationService.resolveByDomain()method can match user email domains to organizations during onboarding. - Manager sync from IdP: When the IdP provides manager claims,
manager_idon the user table is synced automatically.