ACL and User Management
Papr Memory provides enterprise-grade security through a comprehensive Access Control List (ACL) system and robust multi-tenancy support. This page explains how to manage access control and implement multi-tenant isolation.
User Management and Access Control
Papr Memory integrates user management with its access control system, allowing you to create users and manage memories for specific users within your security framework.
Creating and Managing Users
# Create a new user
user = client.user.create(
external_id="user123",
email="user@example.com",
metadata={
"name": "John Doe",
"role": "developer",
"team": "Engineering"
},
type="developerUser" # Options: "developerUser", "user", "agent"
)
# Retrieve user details
user_details = client.user.get(user.user_id)
# Update user information
updated_user = client.user.update(
user_id=user.user_id,
email="updated.user@example.com",
metadata={
"name": "John Doe",
"role": "senior developer",
"team": "Engineering"
}
)
# Delete a user if needed
delete_response = client.user.delete(user.user_id)
# Bulk create users for improved performance
bulk_users = client.user.create_batch(
users=[
{"external_id": "user123", "email": "user123@example.com"},
{"external_id": "user456", "email": "user456@example.com"},
{"external_id": "user789", "email": "user789@example.com"}
]
)
Using External User IDs
Papr Memory supports working with external user IDs directly without requiring explicit user creation. This simplifies onboarding and integration with your existing user management systems.
# Add memory with external user ID (user will be created automatically)
memory = client.memory.add(
content="Customer feedback on feature X",
type="text",
metadata={
"external_user_id": "customer_12345", # No need to create user first
"topics": "feedback, feature X",
"hierarchical_structures": "Feedback/Features"
}
)
# Batch add memories using external user ID
batch_response = client.memory.add_batch(
external_user_id="customer_12345", # External ID as top-level parameter
memories=[
{
"content": "First interaction with customer",
"type": "text",
"metadata": {"topics": "onboarding, first contact"}
},
{
"content": "Follow-up meeting notes",
"type": "text",
"metadata": {"topics": "meeting, follow-up"}
}
]
)
# Search memories by external user ID
search_results = client.memory.search(
query="Find customer feedback",
external_user_id="customer_12345" # Filter by external ID
)
How External User ID Processing Works
When using an external user ID:
- The system checks if a user with this external ID already exists
- If not found, a new user is automatically created in the background
- Memory items are associated with this resolved internal user
Benefits of this approach:
- Simplified onboarding - no separate user creation required
- Seamless integration with existing user management systems
- Reduced API calls for common workflows
When to use explicit user creation vs. automatic creation:
- Use explicit creation when you need to set specific user metadata or types
- Use automatic creation for simpler workflows or rapid prototyping
- Both approaches can coexist in the same application
Performance Optimization: Pre-creating Users
For applications with many users or high transaction volumes, pre-creating users in bulk can significantly improve performance:
# Pre-create users in bulk before they start using the system
bulk_users = client.user.create_batch(
users=[
{"external_id": "customer_1", "metadata": {"plan": "enterprise"}},
{"external_id": "customer_2", "metadata": {"plan": "pro"}},
{"external_id": "customer_3", "metadata": {"plan": "free"}},
# ... hundreds or thousands more users
]
)
Benefits of pre-creating users:
- Eliminates the overhead of creating users on-the-fly during memory operations
- Reduces latency for first-time users' memory operations
- Allows you to set up user metadata, permissions, and configurations in advance
- Enables batch processing of user creation instead of individual API calls
- Ensures consistent user settings across your application
When to consider pre-creating users:
- When onboarding large numbers of users from an existing system
- For applications with high transaction volumes
- When user metadata and permissions need to be established before first use
- In enterprise environments where user provisioning is a separate process
You can still use external_user_id directly in all memory operations even after pre-creating users.
Managing User-Specific Memories with Access Control
Access control is implemented through memory metadata fields that specify which users, workspaces, or roles have read or write access.
# Add memory with specific user permissions
memory = client.memory.add(
content="Confidential project notes",
type="text",
metadata={
"external_user_id": "user123",
"topics": "project, confidential, planning",
"user_read_access": ["user123", "admin_user"],
"user_write_access": ["user123"]
}
)
# Search memories with user context
results = client.memory.search(
query="Find confidential project notes",
external_user_id="user123" # Filter by external ID
)
Access Control with External User IDs
You can also use external user IDs in your access control lists:
# Create memory with external user IDs in access control lists
shared_memory = client.memory.add(
content="Project plan for Team Alpha",
type="text",
metadata={
"external_user_id": "manager_1234", # Owner by external ID
"external_user_read_access": ["manager_1234", "engineer_5678", "designer_9012"],
"external_user_write_access": ["manager_1234"]
}
)
User-Specific Batch Operations
# Batch add memories for a specific user
response = client.memory.add_batch(
memories=[
{
"content": "Meeting notes from team standup",
"metadata": {"user_id": user.user_id, "topics": "meeting, standup"}
},
{
"content": "Action items from planning session",
"metadata": {"user_id": user.user_id, "topics": "planning, tasks"}
}
]
)
Sharing Memories Between Users
Papr Memory allows sharing memories between users within a workspace or organization. This can be achieved in several ways:
Public vs Private Memories
By default, memories can be scoped by:
- Private to a user: Only accessible to the specific user
- Shared with specific users: Accessible to a defined list of users
- Available to a workspace: Accessible to all users in a workspace
- Organization-wide: Available across the entire organization
# Create a private memory (only for user_123)
private_memory = client.memory.add(
content="My personal notes",
type="text",
metadata={
"user_id": "user_123",
"user_read_access": ["user_123"],
"user_write_access": ["user_123"]
}
)
# Create a memory shared with specific users
shared_memory = client.memory.add(
content="Project plan for Team Alpha",
type="text",
metadata={
"user_id": "user_123",
"user_read_access": ["user_123", "user_456", "user_789"],
"user_write_access": ["user_123", "user_456"]
}
)
# Create a workspace-accessible memory
workspace_memory = client.memory.add(
content="Company-wide announcement",
type="text",
metadata={
"user_id": "user_123",
"workspace_id": "workspace_001",
"workspace_read_access": ["workspace_001"]
}
)
# Create an organization-wide memory
org_memory = client.memory.add(
content="Quarterly results",
type="text",
metadata={
"user_id": "user_123",
"role_read_access": ["all_employees"] # Using a role that all employees have
}
)
Updating Memory Sharing Settings
You can change the sharing settings of a memory by updating its metadata:
# Update memory to add additional user access
updated_memory = client.memory.update(
memory_id="mem_123",
metadata={
"user_read_access": ["user_123", "user_456", "user_789", "user_new"],
"user_write_access": ["user_123", "user_new"]
}
)
# Change a private memory to be workspace-accessible
public_memory = client.memory.update(
memory_id="mem_private",
metadata={
"workspace_read_access": ["workspace_001"],
"role_read_access": ["manager", "developer"]
}
)
Searching Shared Memories
When searching, you can access all memories shared with you, including those created by other users:
# Search accessible memories including those shared by others
shared_results = client.memory.search(
query="Project plan",
# No user_id filter means search all accessible memories
)
# Search within a specific workspace
workspace_results = client.memory.search(
query="Company announcement",
metadata={
"workspace_id": "workspace_001"
}
)
Workspace-Based Multi-Tenancy
In the Papr API, multi-tenancy is implemented primarily through the workspace concept. Each memory can be associated with a workspace, and access controls can be applied at the workspace level.
Access Control via Metadata
Access control is implemented through metadata fields in the memory objects:
{
"metadata": {
"user_id": "user_123",
"external_user_id": "customer_456",
"workspace_id": "workspace_789",
"user_read_access": ["user_123", "user_456"],
"user_write_access": ["user_123"],
"external_user_read_access": ["customer_456", "customer_789"],
"external_user_write_access": ["customer_456"],
"workspace_read_access": ["workspace_789"],
"workspace_write_access": [],
"role_read_access": ["admin", "developer"],
"role_write_access": ["admin"]
}
}
Access Control Lists (ACL)
Permission Levels Through Metadata
User-Level Permissions
{ "metadata": { "user_read_access": ["user_123", "user_456"], "user_write_access": ["user_123"], "external_user_read_access": ["customer_123", "customer_456"], "external_user_write_access": ["customer_123"] } }
Workspace-Level Permissions
{ "metadata": { "workspace_read_access": ["workspace_789", "workspace_012"], "workspace_write_access": ["workspace_789"] } }
Role-Level Permissions
{ "metadata": { "role_read_access": ["admin", "editor", "viewer"], "role_write_access": ["admin", "editor"] } }
Role-Based Access Control
While the API does not provide explicit endpoints for role management, you can implement role-based access control through the metadata fields. Common roles might include:
- Admin: Full access to create, read, update and delete memories
- Editor: Can read and update memories but not delete them
- Viewer: Read-only access to memories
These roles are implemented by consistently using the same role identifiers in the role_read_access
and role_write_access
metadata fields.
Implementation
Authentication
The Papr API supports multiple authentication methods:
# Initialize client with API key
client = PaprMemory(api_key="your_api_key")
# Or initialize with bearer token
client = PaprMemory(bearer_token="your_bearer_token")
# Or initialize with session token
client = PaprMemory(session_token="your_session_token")
Implementing Access Control
# Create memory with access control
memory = client.memory.add(
content="Quarterly financial report",
type="text",
metadata={
"workspace_id": "finance_workspace",
"topics": "finance, quarterly, confidential",
"user_read_access": ["finance_team_lead", "cfo", "ceo"],
"user_write_access": ["finance_team_lead"],
"role_read_access": ["finance_department", "executive"],
"role_write_access": ["finance_department_lead"]
}
)
# Filter memories by access control in search
results = client.memory.search(
query="Find quarterly financial reports",
metadata={
"user_id": "finance_team_lead",
"workspace_id": "finance_workspace"
}
)
Security Features
Data Isolation
- Isolation through metadata-based access control
- Separate metadata filtering for searches
- Memory-level access restrictions
Access Controls
- Fine-grained permissions through metadata
- User, workspace, and role-based access
- Extensible metadata attributes
Audit Logging
- Access logs through memory interactions
- Search and retrieval tracking
- Content modification history
Compliance
- GDPR compliance through metadata filtering
- Data retention controls
- Access control for regulatory requirements
Best Practices
Access Control Design
- Establish consistent naming conventions for roles
- Create logical workspace structures
- Document access control schema
- Regularly review access patterns
User Management
- Use descriptive external IDs
- Add comprehensive user metadata
- Maintain up-to-date user records
- Plan for user lifecycle management
- Consider automatic user creation via external_user_id for simpler workflows
Security
- Rotate API keys regularly
- Apply principle of least privilege in access controls
- Monitor access patterns
- Implement rate limiting
Compliance
- Document data flows
- Set retention policies
- Implement comprehensive metadata
- Regular compliance reviews
Next Steps
- Explore Authentication Guide