Skip to main content

Overview

Document-level ACL (Access Control Lists) lets you build multi-user applications on top of Mixpeek where each end-user only sees the documents they are authorized to access. Instead of managing separate namespaces per user, you store all documents in a single namespace and let Mixpeek enforce read/write permissions automatically. Key capabilities:
  • Automatic filter injection — user-scoped API keys transparently filter all reads so users only see their own (or shared) documents
  • Per-document ownership — every document has an _acl object tracking who can read, write, or own it
  • Public documents — mark a document as public: true to make it visible to all users
  • Zero application-side filtering — your app does not need to add user filters to queries; Mixpeek handles it server-side

Concepts

API Key Types

Mixpeek supports two types of API keys:
Key TypePrefixACL Behavior
Org-scopedmxp_sk_Full access — bypasses ACL filters entirely
User-scopedusr_sk_Restricted — ACL filters injected on every read and write
Org-scoped keys are for your backend services and admin operations. User-scoped keys are for end-user sessions in your application.

The _acl Object

Every document stores an _acl sub-object inside its _internal payload:
{
  "_internal": {
    "_acl": {
      "owner": "user_123",
      "read": ["user_123", "user_456"],
      "write": ["user_123"],
      "public": false
    }
  }
}
_acl.owner
string
required
The principal_id of the user who created the document. Only the owner (or an org-scoped key) can modify ACL settings.
_acl.read
string[]
required
List of principal_id values that can read this document.
_acl.write
string[]
required
List of principal_id values that can update this document.
_acl.public
boolean
required
When true, the document is visible to all user-scoped keys regardless of the read list.

Creating User-Scoped API Keys

Generate a user-scoped key by providing a principal_id that represents the end-user in your application (e.g., your internal user ID, email, or UUID).
from mixpeek import Mixpeek

# Use your org-scoped key to create a user-scoped key
client = Mixpeek(api_key="mxp_sk_your-org-key")

user_key = client.organizations.api_keys.create(
    principal_id="user_123",
    key_type="user_scoped",
    description="Key for user 123"
)

print(user_key.api_key)  # usr_sk_...
Store the returned usr_sk_ key securely. Pass it to your frontend or mobile app so the end-user’s requests are automatically scoped.

How ACL Works

Automatic ACL on Document Creation

When a user-scoped key creates a document, Mixpeek automatically sets the _acl:
  • owner = the key’s principal_id
  • read = [principal_id]
  • write = [principal_id]
  • public = false
You do not need to set _acl manually — it is populated from the API key’s identity.

Automatic Filter Injection on Reads

Every read operation made with a user-scoped key — including retriever executions, document listings, and searches — has an ACL filter injected server-side. The filter ensures the user only sees documents where:
  1. Their principal_id is in the _acl.read list, OR
  2. The document has _acl.public: true
This happens transparently. Your application code does not need to add any user-specific filters.

Write Protection

Update and delete operations made with a user-scoped key check the _acl.write list. If the user’s principal_id is not in the write list, the operation returns a 403 Forbidden error.

Org-Scoped Key Bypass

Org-scoped keys (mxp_sk_ prefix) bypass all ACL checks. They can read, write, and modify any document regardless of its _acl settings. Use org-scoped keys for backend services, admin dashboards, and data pipelines.

Managing ACL

Update a document’s ACL using the dedicated endpoint. Only the document owner or an org-scoped key can modify ACL settings.
# Share a document with another user (add read access)
client = Mixpeek(api_key="usr_sk_owner-key")

client.collections.documents.update_acl(
    collection_id="col_abc123",
    document_id="doc_xyz789",
    read=["user_123", "user_456"],  # Add user_456 to readers
)

Making a Document Public

Set public: true to make a document visible to all user-scoped keys in the namespace:
curl -X PATCH https://api.mixpeek.com/v1/collections/col_abc123/documents/doc_xyz789/acl \
  -H "Authorization: Bearer usr_sk_owner-key" \
  -H "Content-Type: application/json" \
  -d '{
    "public": true
  }'

Revoking Access

Remove a user from the read or write list by sending the updated list without their principal_id:
# Remove user_456 from readers — only user_123 retains access
curl -X PATCH https://api.mixpeek.com/v1/collections/col_abc123/documents/doc_xyz789/acl \
  -H "Authorization: Bearer usr_sk_owner-key" \
  -H "Content-Type: application/json" \
  -d '{
    "read": ["user_123"],
    "write": ["user_123"]
  }'

Examples

A SaaS app where each user uploads and searches their own documents, with optional sharing.
1

Create user-scoped keys for each user

When a user signs up, generate a usr_sk_ key with their user ID as the principal_id.
2

Users ingest documents with their key

Each document is automatically tagged with the user’s ACL. User A cannot see User B’s documents.
3

Users search normally

Retriever executions with a user-scoped key only return documents the user can access — no extra filters needed.
4

Share a document

User A shares a document with User B by adding User B’s principal_id to the read list via the ACL endpoint.

Public Knowledge Base with Private Uploads

Combine public reference documents with private user uploads in the same namespace:
# Admin creates public reference docs using org-scoped key
admin_client = Mixpeek(api_key="mxp_sk_org-key")

# Create a document and make it public
doc = admin_client.collections.documents.create(
    collection_id="col_kb",
    data={"title": "Getting Started Guide", "content": "..."}
)

admin_client.collections.documents.update_acl(
    collection_id="col_kb",
    document_id=doc.document_id,
    public=True,
)

# End-user creates private notes using their user-scoped key
user_client = Mixpeek(api_key="usr_sk_user-key")

user_client.collections.documents.create(
    collection_id="col_kb",
    data={"title": "My private notes", "content": "..."}
)
# _acl automatically set: owner=user, read=[user], write=[user], public=false
When the end-user searches, they see both the public reference docs and their own private notes — but not other users’ private documents.

Backwards Compatibility

Documents created before ACL was enabled do not have an _acl field. These documents follow these rules:
Key TypeBehavior for Documents Without _acl
Org-scopedFull access — documents are visible and writable as before
User-scopedNot visible — documents without _acl are excluded from reads
If you adopt user-scoped keys on an existing namespace, pre-existing documents will be invisible to end-users until you set their _acl (either individually or via a bulk update). Org-scoped keys continue to work normally.
To backfill ACL on existing documents, use a bulk update with an org-scoped key:
curl -X POST https://api.mixpeek.com/v1/collections/col_abc123/documents/bulk-update \
  -H "Authorization: Bearer mxp_sk_org-key" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {},
    "update": {
      "_internal._acl": {
        "owner": "system",
        "read": [],
        "write": [],
        "public": true
      }
    }
  }'

Best Practices

Use your own stable user identifiers (UUIDs, database IDs) as principal_id values. Avoid using email addresses, which can change.
Keep org-scoped keys on your backend only. Never expose mxp_sk_ keys to client-side code. User-scoped usr_sk_ keys are safe to use in frontend and mobile apps.
ACL is enforced at the Mixpeek API layer. If you have direct access to the underlying Qdrant or MongoDB instances, ACL filters are not applied. Always route end-user traffic through the Mixpeek API.
  • Security & Tenancy — org-level authentication and namespace isolation
  • Filters — manual query filters (ACL filters are injected automatically on top of these)
  • Namespaces — data isolation boundaries