Multi-Organizations
Karrio’s multi-organization system provides enterprise-grade multi-tenancy with complete data isolation, dual authentication architecture, and sophisticated permission management. Built for SaaS platforms, enterprise deployments, and complex organizational structures.
Architecture Overview
System Architecture
Karrio implements a hub-and-spoke multi-tenant architecture with complete data isolation:
- Organizations serve as tenant boundaries with automatic API token generation
- Resource Isolation creates secure data silos for all resources (20+ resource types)
- Dual Authentication supports both JWT tokens and organization-specific API tokens
- Middleware Layer automatically resolves organization context and filters queries
- IAM Integration provides role-based permissions with context-aware enforcement
Multi-Organization Architecture
Complete tenant isolation with Link models, dual authentication, and automatic context resolution
Data Isolation Architecture
Authentication Flow
Core Features
Complete Data Isolation
Every resource in Karrio is isolated by organization through sophisticated data isolation patterns:
Resource Isolation Architecture
20+ resource types ensure complete data separation1const isolatedResources = [ 2 "Carrier connections", // Shipping provider integrations 3 "Shipments", // Shipping operations 4 "Orders", // Order management 5 "Webhooks", // Event notifications 6 "Templates", // Document templates 7 "Trackers", // Package tracking 8 "Addresses", // Address book 9 "Rates", // Rate configurations 10 // ... 12+ more resource types 11]; 12 13// Each resource is automatically scoped to organization 14// Automatic query filtering ensures 100% data separation
Dual Authentication System
Organization-Specific API Tokens
Every organization automatically receives a unique API token with direct context:
Organization token provides automatic context1curl -H "Authorization: Token key_org_abc123def456" \ 2 https://api.karrio.io/graphql 3 4# No additional headers needed - organization context is automatic
JWT with Organization Switching
JWT tokens support multi-organization access with context switching:
JWT with organization selection1curl -H "Authorization: Bearer jwt_token_here" \ 2 -H "X-Org-ID: org_1234567890" \ 3 https://api.karrio.io/graphql 4 5# JWT defaults to user's primary organization if no X-Org-ID
Role-Based Permissions
Four-tier permission system with granular access control:
Permission Hierarchy
1const roles = { 2 owner: { 3 permissions: [ 4 "manage_org_owner", 5 "manage_team", 6 "manage_apps", 7 "manage_carriers", 8 ], 9 description: "Complete organizational control and ownership transfer", 10 }, 11 admin: { 12 permissions: ["manage_team", "manage_apps", "manage_carriers"], 13 description: "Team management and operational control", 14 }, 15 member: { 16 permissions: [ 17 "manage_data", 18 "manage_orders", 19 "manage_shipments", 20 "manage_trackers", 21 ], 22 description: "Core shipping operations and data management", 23 }, 24 developer: { 25 permissions: ["manage_webhooks"], 26 description: "API integrations and webhook management", 27 }, 28};
API Reference
GraphQL API
Core Queries
1query GetOrganizations { 2 organizations { 3 id 4 name 5 slug 6 is_active 7 created 8 token 9 current_user { 10 email 11 is_admin 12 is_owner 13 roles 14 } 15 members { 16 email 17 roles 18 full_name 19 last_login 20 } 21 workspace_config { 22 default_currency 23 default_weight_unit 24 default_dimension_unit 25 insured_by_default 26 } 27 usage { 28 members 29 total_shipments 30 total_shipping_spend 31 api_requests { 32 period 33 count 34 } 35 } 36 } 37}
Response:
1{ 2 "data": { 3 "organizations": [ 4 { 5 "id": "org_1234567890", 6 "name": "Acme Shipping", 7 "slug": "acme-shipping", 8 "is_active": true, 9 "created": "2024-01-15T10:30:00Z", 10 "token": "key_abc123def456", 11 "current_user": { 12 "email": "admin@acme.com", 13 "is_admin": true, 14 "is_owner": true, 15 "roles": ["owner"] 16 }, 17 "members": [ 18 { 19 "email": "admin@acme.com", 20 "roles": ["owner"], 21 "full_name": "John Admin", 22 "last_login": "2024-01-20T08:15:00Z" 23 }, 24 { 25 "email": "user@acme.com", 26 "roles": ["member"], 27 "full_name": "Jane User", 28 "last_login": "2024-01-19T14:22:00Z" 29 } 30 ], 31 "workspace_config": { 32 "default_currency": "USD", 33 "default_weight_unit": "LB", 34 "default_dimension_unit": "IN", 35 "insured_by_default": false 36 }, 37 "usage": { 38 "members": 2, 39 "total_shipments": 145, 40 "total_shipping_spend": 2847.5, 41 "api_requests": [ 42 { 43 "period": "2024-01-20", 44 "count": 234 45 } 46 ] 47 } 48 } 49 ] 50 } 51}
Organization Management Mutations
Create Organization
1mutation CreateOrganization($input: CreateOrganizationMutationInput!) { 2 create_organization(input: $input) { 3 organization { 4 id 5 name 6 is_active 7 created 8 token 9 } 10 errors { 11 field 12 messages 13 } 14 } 15}
Variables:
1{ 2 "input": { 3 "name": "New Shipping Team" 4 } 5}
Response:
1{ 2 "data": { 3 "create_organization": { 4 "organization": { 5 "id": "org_9876543210", 6 "name": "New Shipping Team", 7 "is_active": true, 8 "created": "2024-01-20T15:45:00Z", 9 "token": "key_xyz789uvw456" 10 }, 11 "errors": [] 12 } 13 } 14}
Update Organization
1mutation UpdateOrganization($input: UpdateOrganizationMutationInput!) { 2 update_organization(input: $input) { 3 organization { 4 id 5 name 6 } 7 errors { 8 field 9 messages 10 } 11 } 12}
Delete Organization
1mutation DeleteOrganization($input: DeleteOrganizationMutationInput!) { 2 delete_organization(input: $input) { 3 organization { 4 id 5 name 6 } 7 errors { 8 field 9 messages 10 } 11 } 12}
Team Management Mutations
Send Organization Invitations
1mutation SendOrganizationInvites( 2 $input: SendOrganizationInvitesMutationInput! 3) { 4 send_organization_invites(input: $input) { 5 organization { 6 id 7 name 8 } 9 errors { 10 field 11 messages 12 } 13 } 14}
Variables:
1{ 2 "input": { 3 "org_id": "org_1234567890", 4 "emails": ["newuser@example.com", "colleague@example.com"], 5 "redirect_url": "https://app.karrio.io/accept-invitation", 6 "roles": ["member"] 7 } 8}
Accept Organization Invitation
1mutation AcceptOrganizationInvitation( 2 $input: AcceptOrganizationInvitationMutationInput! 3) { 4 accept_organization_invitation(input: $input) { 5 organization { 6 id 7 name 8 current_user { 9 email 10 is_admin 11 } 12 } 13 errors { 14 field 15 messages 16 } 17 } 18}
Change Organization Owner
1mutation ChangeOrganizationOwner( 2 $input: ChangeOrganizationOwnerMutationInput! 3) { 4 change_organization_owner(input: $input) { 5 organization { 6 id 7 name 8 } 9 errors { 10 field 11 messages 12 } 13 } 14}
Set User Roles
1mutation SetOrganizationUserRoles( 2 $input: SetOrganizationUserRolesMutationInput! 3) { 4 set_organization_user_roles(input: $input) { 5 organization { 6 id 7 name 8 } 9 errors { 10 field 11 messages 12 } 13 } 14}
Workspace Configuration API
Query Workspace Configuration
1query GetWorkspaceConfig { 2 workspace_config { 3 object_type 4 default_currency 5 default_weight_unit 6 default_dimension_unit 7 default_country_code 8 federal_tax_id 9 state_tax_id 10 insured_by_default 11 } 12}
Update Workspace Configuration
1mutation UpdateWorkspaceConfig($input: WorkspaceConfigMutationInput!) { 2 update_workspace_config(input: $input) { 3 workspace_config { 4 object_type 5 default_currency 6 default_weight_unit 7 default_dimension_unit 8 insured_by_default 9 } 10 errors { 11 field 12 messages 13 } 14 } 15}
Variables:
1{ 2 "input": { 3 "default_currency": "EUR", 4 "default_weight_unit": "KG", 5 "default_dimension_unit": "CM", 6 "insured_by_default": true, 7 "federal_tax_id": "FR12345678901" 8 } 9}
Authentication Patterns
Organization Token Usage (Recommended)
Organization tokens provide automatic context and are the preferred method for production integrations:
Organization token automatically sets context1const client = new KarrioClient({ 2 apiKey: "key_org_abc123def456", // Organization-specific token 3 baseUrl: "https://api.karrio.io", 4}); 5 6// All operations automatically scoped to organization 7const shipments = await client.shipments.list(); 8const carriers = await client.carriers.list();
JWT with Organization Switching
JWT tokens support multi-organization access patterns:
JWT with organization context1const client = new KarrioClient({ 2 accessToken: "jwt_token_here", 3 organization: "org_1234567890", 4 baseUrl: "https://api.karrio.io", 5}); 6 7// Switch organization context 8client.setOrganization("org_9876543210"); 9const differentOrgShipments = await client.shipments.list();
Token Precedence Rules
The system follows a specific precedence hierarchy for organization context:
- Organization-specific API token (highest precedence)
- X-Org-ID header with JWT token
- User’s default organization (fallback)
Precedence example - Organization token wins1curl -H "Authorization: Token key_org_abc123" \ 2 -H "X-Org-ID: org_different" \ 3 https://api.karrio.io/graphql 4# Result: Uses organization from token, ignores header
Data Isolation Patterns
Automatic Query Filtering
Organization access control automatically filters all queries:
These queries are automatically scoped to the authenticated organization1const carriers = await client.carriers.list(); // Only org's carriers 2const shipments = await client.shipments.list(); // Only org's shipments 3const orders = await client.orders.list(); // Only org's orders 4const webhooks = await client.webhooks.list(); // Only org's webhooks 5 6// 100% data isolation guaranteed at the database level
Data Isolation Verification
You can verify complete data isolation through testing:
Create resources in Organization A1const orgAClient = new KarrioClient({ apiKey: "key_org_a_token" }); 2const orgACarrier = await orgAClient.carriers.create(carrierData); 3 4// Organization B cannot access Organization A's resources 5const orgBClient = new KarrioClient({ apiKey: "key_org_b_token" }); 6const orgBCarriers = await orgBClient.carriers.list(); 7// Result: Empty list - complete isolation verified
Use Cases & Implementation Patterns
Multi-Tenant SaaS Platform
Perfect for SaaS platforms serving multiple clients:
Client-specific organization setup1const createClientOrganization = async (clientData) => { 2 const response = await adminClient.graphql( 3 ` 4 mutation CreateOrganization($input: CreateOrganizationMutationInput!) { 5 create_organization(input: $input) { 6 organization { 7 id 8 name 9 token 10 workspace_config { 11 default_currency 12 } 13 } 14 } 15 } 16 `, 17 { 18 input: { 19 name: clientData.companyName, 20 }, 21 }, 22 ); 23 24 // Configure workspace for client's region 25 await adminClient.graphql( 26 ` 27 mutation UpdateWorkspaceConfig($input: WorkspaceConfigMutationInput!) { 28 update_workspace_config(input: $input) { 29 workspace_config { 30 default_currency 31 default_country_code 32 } 33 } 34 } 35 `, 36 { 37 input: { 38 default_currency: clientData.currency, 39 default_country_code: clientData.country, 40 }, 41 }, 42 ); 43 44 return response.data.create_organization.organization; 45};
Enterprise Department Management
Ideal for large enterprises with multiple departments:
Department-based organization structure1const departmentOrgs = { 2 fulfillment: "org_fulfillment_123", 3 returns: "org_returns_456", 4 international: "org_intl_789", 5}; 6 7// Department-specific configurations 8const configureDepartment = async (dept, config) => { 9 const client = new KarrioClient({ 10 apiKey: departmentTokens[dept], 11 }); 12 13 await client.updateWorkspaceConfig({ 14 default_currency: config.currency, 15 federal_tax_id: config.taxId, 16 insured_by_default: config.insurance, 17 }); 18};
Partner Ecosystem Management
Enable controlled partner access:
Partner organization with limited permissions1const createPartnerAccess = async (partnerData) => { 2 // Create partner organization 3 const org = await createOrganization(partnerData.name); 4 5 // Create limited API key for partner 6 return await createAPIKey({ 7 label: `${partnerData.name} Integration`, 8 permissions: ["manage_shipments"], // Limited scope 9 organization: org.id, 10 }); 11};
Franchise Management System
Support franchise business models:
Franchise-specific configuration1const setupFranchise = async (franchiseData) => { 2 const organization = await createOrganization(franchiseData.locationName); 3 4 // Configure franchise-specific settings 5 await updateWorkspaceConfig({ 6 default_currency: franchiseData.currency, 7 federal_tax_id: franchiseData.taxId, 8 state_tax_id: franchiseData.stateTaxId, 9 }); 10 11 // Set up franchise-specific carriers 12 await addCarrierConnection({ 13 carrier: "fedex", 14 credentials: franchiseData.fedexAccount, 15 }); 16 17 return organization; 18};
Security & Best Practices
Authentication Security
Token Management
Secure token handling1class SecureKarrioClient { 2 constructor(config) { 3 this.token = config.apiKey; 4 this.organization = config.organization; 5 } 6 7 // Always validate token before use 8 async validateToken() { 9 const response = await this.query(` 10 query ValidateToken { 11 user { 12 id 13 organizations { 14 id 15 name 16 } 17 } 18 } 19 `); 20 return response.data.user; 21 } 22 23 // Rotate tokens regularly 24 async rotateToken(password) { 25 return await this.mutate( 26 ` 27 mutation RefreshToken($input: MutateTokenMutationInput!) { 28 mutate_token(input: $input) { 29 token { key } 30 } 31 } 32 `, 33 { 34 input: { 35 key: this.token, 36 refresh: true, 37 password: password, 38 }, 39 }, 40 ); 41 } 42}
Permission Validation
Always verify permissions before operations1const validatePermissions = async (client, requiredPermissions) => { 2 const user = await client.query(` 3 query GetCurrentUser { 4 user { 5 organizations { 6 current_user { 7 roles 8 } 9 } 10 } 11 } 12 `); 13 14 const userRoles = user.data.user.organizations[0].current_user.roles; 15 return requiredPermissions.every((perm) => 16 rolePermissions[userRoles[0]].includes(perm), 17 ); 18};
Data Security
Audit Logging
Track organization activities1const auditAction = async (action, resource, organization) => { 2 await logger.log({ 3 action: action, 4 resource: resource, 5 organization: organization, 6 timestamp: new Date(), 7 user: getCurrentUser(), 8 }); 9}; 10 11// Example usage 12await auditAction("CREATE_SHIPMENT", shipment.id, org.id);
Data Retention
Organization-specific data retention1const cleanupOrganizationData = async (orgId, retentionDays) => { 2 const cutoffDate = new Date(); 3 cutoffDate.setDate(cutoffDate.getDate() - retentionDays); 4 5 // Clean up old shipments, orders, etc. 6 await client.cleanupShipments({ 7 organization: orgId, 8 before: cutoffDate, 9 }); 10};
Performance Optimization
Efficient Query Patterns
Batch organization operations1const batchOrganizationQuery = async (orgIds) => { 2 return await client.query(` 3 query GetMultipleOrganizations { 4 organizations { 5 id 6 name 7 usage { 8 total_shipments 9 members 10 } 11 workspace_config { 12 default_currency 13 } 14 } 15 } 16 `); 17}; 18 19// Cache organization configuration 20const orgConfigCache = new Map(); 21 22const getCachedOrgConfig = async (orgId) => { 23 if (!orgConfigCache.has(orgId)) { 24 const config = await getWorkspaceConfig(orgId); 25 orgConfigCache.set(orgId, config); 26 } 27 return orgConfigCache.get(orgId); 28};
Database Optimization
Optimize organization resource queries1const getOrganizationResources = async (orgId, resourceType) => { 2 // Use efficient queries to avoid N+1 problems 3 return await client.query( 4 ` 5 query GetOrgResources($orgId: String!, $type: String!) { 6 organization(id: $orgId) { 7 ${resourceType} { 8 # Select only needed fields 9 id 10 created 11 status 12 } 13 } 14 } 15 `, 16 { orgId, type: resourceType }, 17 ); 18};
Migration & Setup
Adding Organization Support to Existing Systems
Migration helper for existing single-tenant systems1const migrateToMultiOrg = async (defaultOrgName) => { 2 // 1. Create default organization 3 const defaultOrg = await createOrganization(defaultOrgName); 4 5 // 2. Migrate existing resources 6 await migrateExistingResources(defaultOrg.id); 7 8 // 3. Update API clients 9 await updateAPIClients(defaultOrg.token); 10 11 // 4. Verify data isolation 12 await verifyDataIsolation(); 13}; 14 15const migrateExistingResources = async (orgId) => { 16 // Associate existing resources to default organization 17 const resources = ["carriers", "shipments", "orders", "webhooks"]; 18 19 for (const resourceType of resources) { 20 await associateResourcesToOrganization(resourceType, orgId); 21 } 22};
Development Environment Setup
Development organization setup1const setupDevEnvironment = async () => { 2 // Create development organizations 3 const devOrg = await createOrganization("Development Team"); 4 const testOrg = await createOrganization("Testing Environment"); 5 6 // Configure different settings for each 7 await configureDevOrganization(devOrg.id, { 8 currency: "USD", 9 testMode: true, 10 }); 11 12 await configureTestOrganization(testOrg.id, { 13 currency: "EUR", 14 testMode: true, 15 }); 16 17 return { devOrg, testOrg }; 18};
Getting Started
Quick Setup Guide
-
Enable Multi-Organization Mode
1export MULTI_ORGANIZATIONS=True
-
Create Your First Organization
1const organization = await client.createOrganization({ 2 name: "My Shipping Company", 3});
-
Configure Workspace Settings
1await client.updateWorkspaceConfig({ 2 default_currency: "USD", 3 default_weight_unit: "LB", 4 insured_by_default: false, 5});
-
Invite Team Members
1await client.sendOrganizationInvites({ 2 org_id: organization.id, 3 emails: ["teammate@company.com"], 4 roles: ["member"], 5});
-
Use Organization Token
1const orgClient = new KarrioClient({ 2 apiKey: organization.token, 3}); 4 5// All operations automatically scoped to organization 6const carriers = await orgClient.carriers.list();
Next Steps
- Learn about user management for detailed user administration
- Explore webhooks for organization activity notifications
- Set up API logs for organization-level monitoring
- Configure admin console for centralized management