Overview
Permissions control which documents an end-user can see when they search. Mixpeek enforces them server-side at retrieval time, so a user’s query only ever returns documents they are authorized to access — your application never has to add per-user filters, and there is no way for a client to bypass them. There are two ways to drive permissions, and they share the same enforcement path:Built-in document ACL
Mixpeek stores and enforces an
_acl (owner / read / write / public) on each
document, keyed on user-scoped API keys. Best when Mixpeek is your source of
truth for who-can-see-what.External authorization (OpenFGA)
Keep your existing permissions in your own OpenFGA
deployment; Mixpeek queries it at retrieval time and filters results to match.
Best when an external system already owns authorization.
Which should I use?
| Built-in document ACL | External authorization (OpenFGA) | |
|---|---|---|
| Source of truth | Mixpeek (_acl on each document) | Your OpenFGA store |
| Grant model | read list + public flag | Your full OpenFGA model (groups, roles, folder inheritance, …) |
| Best for | Apps where Mixpeek owns permissions | Apps where an external system already owns permissions |
| Setup | Create user-scoped keys; ACL set automatically | Run OpenFGA; opt the namespace in; map subjects |
| Consistency | Strong (enforced inline) | Strong (pull) or eventual (push) — you choose |
External authorization (OpenFGA)
When a namespace opts in, Mixpeek acts as a relying party on your OpenFGA: at retrieval time it asks OpenFGA what the acting user can see and filters the results accordingly. Your OpenFGA remains the single source of truth.How it works
- The acting subject is the
principal_idof the user-scoped API key executing the query, mapped to an OpenFGA subject —user:<principal_id>by default. - Mixpeek queries OpenFGA for that subject using one of the strategies below.
- Results are filtered — unauthorized documents are removed before the response is returned, for both ad-hoc and saved retrievers.
document_id. By default Mixpeek checks the relation viewer on the
object type document:
Enforcement modes
Different strategies trade consistency against scale. Pick one withmode:
mode | Strategy | Use when |
|---|---|---|
auto (default) | ListObjects → pre-filter when the accessible set is small (≤ list_objects_max), otherwise BatchCheck post-filter | You want sensible behavior at any size |
pull_list_objects | Always ListObjects → document_id pre-filter | The accessible set per user is small (≤ ~1000) |
pull_batch_check | Run the search, then BatchCheck the candidates and drop unauthorized | Users can access many documents |
push | Sync OpenFGA grants into an indexed field and filter in-index | Lowest query latency; eventual consistency is acceptable |
Pre-filtering via
ListObjects only scales to a small accessible set
(~1000 objects). For larger corpora, BatchCheck post-filtering over-fetches
by over_fetch_factor (default 2×) from the vector store so the page stays
full after unauthorized documents are dropped. auto switches between the two
for you.Configuration
Opt a namespace in by settinginfrastructure.authorization:
Master switch.
false (default) means the namespace is unchanged — no
enforcement and no calls to OpenFGA.Authorization backend.
openfga is the supported provider.Base URL of your OpenFGA HTTP API, reachable from Mixpeek.
The OpenFGA store id holding your relationship tuples.
Authorization-model id. When omitted, the store’s latest model is used.
Bearer token for your OpenFGA API, if it requires one.
The OpenFGA object type representing a Mixpeek document.
The relation that grants read/retrieve access.
Enforcement strategy:
auto, pull_list_objects, pull_batch_check, or push.Post-filter over-fetch multiplier (≥ 2 recommended) so a page stays full after
unauthorized documents are dropped.
Setup
Run OpenFGA and write your tuples
Stand up OpenFGA (or use your existing deployment) and write
viewer tuples
whose object ids are Mixpeek document_ids, e.g.
document:doc_abc123 # viewer @ user:alice. Use user:* for public documents.Create user-scoped keys per end-user
Generate a
usr_sk_ key whose principal_id matches the OpenFGA user id
(e.g. alice). Create it with your org-scoped key via
POST /v1/organizations/api-keys
using key_type: "user_scoped" and principal_id.Keeping permissions current (push mode)
Inpush mode, Mixpeek subscribes to your OpenFGA changelog and projects grants
onto an indexed field on each document, then filters in-index for the lowest
query latency. Grant and revocation changes propagate within the sync interval
(eventually consistent). For strict point-in-time consistency, use auto or
pull_batch_check, which evaluate against OpenFGA live on every query.
Behavior and guarantees
- Fail-closed. If OpenFGA is unreachable, queries return the safe subset (fewer results), never unauthorized documents.
- No caching leaks. When authorization is active, the per-retriever result cache is bypassed so one user’s authorized page can never be served to another.
- Both retrieval paths. Ad-hoc and saved retrievers enforce identically.
- Opt-out is unchanged. A namespace with
enabled: false(or noauthorizationblock) behaves exactly as before.
Related
- Document Access Control — Mixpeek’s built-in
_aclpermissions - Operate — authentication, secrets, and namespace isolation
- Filters — manual query filters (permission filters are injected on top)
- OpenFGA documentation — modeling, tuples, and the Check / ListObjects / BatchCheck APIs

