Access control is opt-in per request. A query without an
acl field is not filtered, exactly as before. Adding ACLs to your documents changes nothing until your queries start declaring who is asking.1. The two halves
Access control only works when both halves are in place. Either half alone is a no-op.2. Principals
A principal is one string identifying who may retrieve a document. Five forms:
Principals are lowercased, trimmed, and deduplicated on the way in.
__public__ overrides everything else in the same list - a document that is public is public. A list containing __private__ alongside real principals keeps the real principals and drops the sentinel; __private__ only means something on its own.
Limits: 1000 principals per document, 256 characters per principal. Past that, use a group: or domain: principal - a list of several thousand individuals is organizational structure, not an allow-list.
3. Set an ACL
At ingest, on an app source
Each item inapp_knowledge accepts an acl list:
acl and the document is unrestricted. A malformed principal rejects the whole request with 400 rather than ingesting the document unprotected.
On an existing source, without re-ingesting
PATCH /context/{id}/metadata accepts acl and replaces the stored list:
An
acl-only body is a valid edit; you do not have to send metadata alongside it.
On a connector resource
Every object synced from a resource inherits the resource’s rule. Set it when you configure the connector:["__public__"] to open a resource back up.
4. Let connectors capture permissions for you
For supported providers, HydraDB reads the source app’s own permissions on every sync and applies them as ACLs, so you do not maintain a parallel permission model.
Check what is live for your account with
GET /connector-catalog: each provider carries rbac_support and a one-line rbac_description of what it captures.
Precedence, when both exist:
- A per-document permission from the provider (a Drive file’s own sharing) wins over everything.
- A provider verdict of “this resource is public” will not override a rule you set. Restricting a public channel is a deliberate act, and enabling capture never widens it back.
- Otherwise the provider’s resource-level verdict wins over your rule, because the provider is the fresher source of truth.
Capture is per provider and can be turned off without a deploy. A provider without capture is not broken - your own rules still work on it, and documents remain unrestricted until you set one.
5. Query on behalf of someone
Pass the caller’s identity asacl on POST /query (and POST /context/list, which takes the same field with the same meaning):
- its stored ACL is absent or empty (unrestricted),
- it is
__public__, - its ACL contains one of the caller’s principals.
__public__is added for you. You never have to ask for public content.- The domain principal is derived from the email. Querying as
[email protected]automatically matches anything shared withdomain:acme.com.
"acl": ["[email protected]", "group:slack:C0123"].
Access control composes with, and is independent of, metadata filters: filters express what you are looking for, ACLs express what you are allowed to find. A caller cannot widen their own visibility with a filter.
6. Revoking access
Deleting a rule does not widen access on its own: visibility only ever widens from a value you positively set. To open a restricted resource back up, set its ACL to
["__public__"] rather than clearing it.
7. Common mistakes
ACLs are set but every query still returns everything
ACLs are set but every query still returns everything
The queries are not declaring an identity.
acl is opt-in per request; without it there is no filtering. Add "acl": ["<caller email>"] to the query.Sending an empty list to mean 'no restriction'
Sending an empty list to mean 'no restriction'
"acl": [] means nobody, and is stored as __private__. To leave a document unrestricted, omit the field entirely. To make it visible to everyone, send ["__public__"].Adding a person by sending only their email in a metadata edit
Adding a person by sending only their email in a metadata edit
The
acl on PATCH /context/{id}/metadata replaces the list, it does not merge. Send the complete new allow-list every time.Expecting connector capture to apply to already-synced documents instantly
Expecting connector capture to apply to already-synced documents instantly
A provider-side permission change is picked up on the next sync cycle for that resource. A rule you set yourself through the API applies to already-synced documents on the next query, with no sync involved.
A caller who should see a document sees nothing
A caller who should see a document sees nothing
Check the principal forms on both sides.
group:slack:C0123 on the document only matches a query that declares that same group; unlike domain:, group membership is not derived from the caller’s email. Also confirm the email matches exactly - principals are compared after lowercasing and trimming, but not otherwise fuzzy-matched.Related
- Connectors - syncing app data, and per-resource ACL rules
- App Sources - the ingest shape that carries
acl - Query - the
aclfield alongside every other retrieval parameter - Metadata - filtering by attributes, a different question from permission
- Multi-Tenant Support - databases and collections, the isolation boundary ACLs work inside
- Update Source Metadata - API Reference - the
aclreplacement contract
