📖 Looking for karrio's legacy docs? Visit docs.karrio.io

Multi-Organizations

Insiders
Enterprise

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 separation
1const 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 context
1curl -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 selection
1curl -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 tokens provide automatic context and are the preferred method for production integrations:

Organization token automatically sets context
1const 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 context
1const 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:

  1. Organization-specific API token (highest precedence)
  2. X-Org-ID header with JWT token
  3. User’s default organization (fallback)
Precedence example - Organization token wins
1curl -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 organization
1const 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 A
1const 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 setup
1const 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 structure
1const 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 permissions
1const 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 configuration
1const 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 handling
1class 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 operations
1const 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 activities
1const 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 retention
1const 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 operations
1const 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 queries
1const 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 systems
1const 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 setup
1const 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

  1. Enable Multi-Organization Mode

    1export MULTI_ORGANIZATIONS=True
  2. Create Your First Organization

    1const organization = await client.createOrganization({ 2 name: "My Shipping Company", 3});
  3. Configure Workspace Settings

    1await client.updateWorkspaceConfig({ 2 default_currency: "USD", 3 default_weight_unit: "LB", 4 insured_by_default: false, 5});
  4. Invite Team Members

    1await client.sendOrganizationInvites({ 2 org_id: organization.id, 3 emails: ["teammate@company.com"], 4 roles: ["member"], 5});
  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