Skip to content
Permissions

Permissions

How the SDK gates operations: the Operation enum, permission domains, the built-in classes, and how to write your own. Every service (AgentService, ThreadService, MemoryService, WorkflowService, as_view(), history()) checks permissions before acting and raises PermissionDenied on failure.

Operations and Domains

An operation is the unit of access control. A domain is the default permission chain for a group of operations:

from django_ai_sdk.permissions import Operation, PermissionDomain

Operation.CHAT            # "chat"
PermissionDomain.MEMORY   # "memory"
Domain Default class
agent AgentDefaultPermission
thread ThreadDefaultPermission
memory MemoryDefaultPermission
integrations IntegrationDefaultPermission
workflow WorkflowDefaultPermission

Override a domain globally via AI_SDK_PERMISSIONS:

AI_SDK_PERMISSIONS = {
    "memory": ["apps.memories.permissions.AllowAnonymousMemoryPermission"],
    "thread": ["apps.agents.permissions.DemoThreadPermission"],
}

The Operation Enum

# Agent
VIEW_AGENT, CREATE_AGENT, UPDATE_AGENT, DELETE_AGENT
# Threads
LIST_THREADS, CREATE_THREAD, VIEW_THREAD, UPDATE_THREAD, DELETE_THREAD,
DELETE_ALL_THREADS, VIEW_HISTORY
# Messages
VIEW_MESSAGES, SEND_MESSAGE, RATE_MESSAGE, DELETE_MESSAGE, RESTORE_MESSAGE
# Files
UPLOAD_FILE, DELETE_FILE, VIEW_FILE
# Memories
VIEW_MEMORY, CREATE_MEMORY, UPDATE_MEMORY, DELETE_MEMORY,
VIEW_DOCUMENT, LIST_DOCUMENTS, UPLOAD_DOCUMENT, DELETE_DOCUMENT,
LIST_THREAD_MEMORIES, LINK_MEMORY, UNLINK_MEMORY
# Integrations
USE_INTEGRATION, MANAGE_INTEGRATION
# Workflows
VIEW_WORKFLOW, RUN_WORKFLOW, MANAGE_WORKFLOW
# Misc
CHAT, REINDEX

Built-in Permission Classes

Class Behavior
AllowAll Grants everything.
DenyAll Denies everything.
IsAuthenticated Allows any authenticated user.
IsAdminUser Allows staff or superusers.
IsInAllowedGroups(groups=[...]) Allows authenticated users in one of the named auth groups.
IsOwner(field="user_id") Allows the object’s owner; field selects the ownership attribute.

Tiered default permissions

Four built-ins expose READ / WRITE / MANAGE operation tiers:

  • ThreadDefaultPermission: authenticated users get full access to their own threads; anonymous users get none.
  • MemoryDefaultPermission: can_manage members get everything; can_manage=False members get read + write entries; public read-only is gated by is_public; anonymous is always blocked.
  • AgentDefaultPermission: can_manage members manage the agent; other members view and use it; is_public grants read and use to anyone; anonymous is otherwise blocked for agent operations and passes through everything else. Chat, thread and file operations have no object to check, so they are gated on the agent passed as the agent keyword.
  • WorkflowDefaultPermission: any authenticated user may author a workflow and run an active one; a stored definition is managed only by its created_by; a run is read only by the principal it ran as, since its outputs are that person’s content. Staff manage and read every one of both. It implements get_queryset_perms, so list endpoints are scoped in the database rather than filtered afterwards — see Workflows.

MemoryService.get_object_permissions_map() and agent_permissions() auto-discover these READ/WRITE/MANAGE tiers and return an ObjectPermissions (can_read / can_write / can_manage) for the frontend.

The BasePermission API

class BasePermission(ABC):
    async def has_permission(self, user, operation, **kwargs) -> bool: ...
    async def has_object_permission(self, user, operation, obj, **kwargs) -> bool: ...
    def get_queryset_perms(self, user, operation, queryset) -> QuerySet: ...

has_permission gates the operation itself; has_object_permission gates it on a specific object. Implementing get_queryset_perms lets list endpoints filter at the database level instead of post-checking every row: MemoryDefaultPermission uses it for VIEW_MEMORY.

Writing a Custom Permission

from django_ai_sdk.permissions import BasePermission, Operation

class IsSalesTeam(BasePermission):
    async def has_permission(self, user, operation, **kwargs) -> bool:
        if user is None or not user.is_authenticated:
            return False
        return await user.groups.filter(name="Sales").aexists()

Parameterized permissions are instances, not classes:

class IsInAllowedGroups(BasePermission):
    def __init__(self, groups=None):
        self.groups = list(groups or [])

    async def has_permission(self, user, operation, **kwargs) -> bool:
        return user.is_authenticated and await user.groups.filter(name__in=self.groups).aexists()

Declare them on agents (permissions = [IsInAllowedGroups(groups=["eng"])]), on integrations (service.permissions), or as a domain override in AI_SDK_PERMISSIONS. Both class and instance forms are accepted anywhere.

PermissionsMixin and Helpers

Services subclass PermissionsMixin and set a domain:

class MyService(PermissionsMixin):
    domain = PermissionDomain.THREAD

    async def do_thing(self, *, user):
        await self.has_perms(user, Operation.VIEW_THREAD, obj, raise_on_deny=True)
  • has_perms(user, operation, obj=None): the check used by the built-in services; raise_on_deny=False returns a boolean instead of raising.
  • get_object_permissions_map(user, obj): returns {"read": ..., "write": ..., "manage": ...} from the tiered classes.
  • has_queryset_perms(user, operation, queryset): runs get_queryset_perms across the domain chain.

The low-level check_permissions() / check_object_permissions() / has_perms() free functions power all of the above and are what you’d use outside a mixin.