Skip to main content

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.
Both are opt-in and fail-closed: a document with no resolvable grant is excluded, never leaked. Namespaces that don’t opt in behave exactly as before.

Which should I use?

Built-in document ACLExternal authorization (OpenFGA)
Source of truthMixpeek (_acl on each document)Your OpenFGA store
Grant modelread list + public flagYour full OpenFGA model (groups, roles, folder inheritance, …)
Best forApps where Mixpeek owns permissionsApps where an external system already owns permissions
SetupCreate user-scoped keys; ACL set automaticallyRun OpenFGA; opt the namespace in; map subjects
ConsistencyStrong (enforced inline)Strong (pull) or eventual (push) — you choose
If you already manage authorization in OpenFGA (or a Zanzibar-style system), use external authorization so you don’t duplicate your permission model. Otherwise the built-in document ACL is the simplest path.

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

  1. The acting subject is the principal_id of the user-scoped API key executing the query, mapped to an OpenFGA subject — user:<principal_id> by default.
  2. Mixpeek queries OpenFGA for that subject using one of the strategies below.
  3. Results are filtered — unauthorized documents are removed before the response is returned, for both ad-hoc and saved retrievers.
Your OpenFGA model must represent each Mixpeek document as an object whose id is the Mixpeek document_id. By default Mixpeek checks the relation viewer on the object type document:
document:doc_abc123 # viewer @ user:alice      # alice can retrieve doc_abc123
document:doc_abc123 # viewer @ user:*          # the document is public
Group, role, and parent-folder inheritance all work — OpenFGA resolves them server-side; Mixpeek only relays the decision.

Enforcement modes

Different strategies trade consistency against scale. Pick one with mode:
modeStrategyUse when
auto (default)ListObjects → pre-filter when the accessible set is small (≤ list_objects_max), otherwise BatchCheck post-filterYou want sensible behavior at any size
pull_list_objectsAlways ListObjectsdocument_id pre-filterThe accessible set per user is small (≤ ~1000)
pull_batch_checkRun the search, then BatchCheck the candidates and drop unauthorizedUsers can access many documents
pushSync OpenFGA grants into an indexed field and filter in-indexLowest 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 setting infrastructure.authorization:
curl -X PATCH https://api.mixpeek.com/v1/namespaces/ns_abc123 \
  -H "Authorization: Bearer mxp_sk_your-org-key" \
  -H "Content-Type: application/json" \
  -d '{
    "infrastructure": {
      "authorization": {
        "enabled": true,
        "provider": "openfga",
        "api_url": "https://openfga.your-company.com",
        "store_id": "01J0XEXAMPLESTORE",
        "relation": "viewer",
        "mode": "auto"
      }
    }
  }'
enabled
boolean
required
Master switch. false (default) means the namespace is unchanged — no enforcement and no calls to OpenFGA.
provider
string
default:"openfga"
Authorization backend. openfga is the supported provider.
api_url
string
required
Base URL of your OpenFGA HTTP API, reachable from Mixpeek.
store_id
string
required
The OpenFGA store id holding your relationship tuples.
model_id
string
Authorization-model id. When omitted, the store’s latest model is used.
api_token
string
Bearer token for your OpenFGA API, if it requires one.
object_type
string
default:"document"
The OpenFGA object type representing a Mixpeek document.
relation
string
default:"viewer"
The relation that grants read/retrieve access.
mode
string
default:"auto"
Enforcement strategy: auto, pull_list_objects, pull_batch_check, or push.
over_fetch_factor
integer
default:"2"
Post-filter over-fetch multiplier (≥ 2 recommended) so a page stays full after unauthorized documents are dropped.

Setup

1

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.
2

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.
3

Opt the namespace in

PATCH the namespace with the infrastructure.authorization block above.
4

Query normally

Execute retrievers with each user’s usr_sk_ key — results are filtered to what that user can see in OpenFGA. No query changes needed.

Keeping permissions current (push mode)

In push 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 no authorization block) behaves exactly as before.
Permissions are enforced at the Mixpeek API layer. If you have direct access to the underlying vector store or database, filters are not applied. Always route end-user traffic through the Mixpeek API.
  • Document Access Control — Mixpeek’s built-in _acl permissions
  • 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