AACSearch

Private fields

Keep fields such as cost price or supplier id searchable for trusted callers while they never reach the widget, the assistant or unprivileged keys.

Some fields belong in the index but not in a public response: cost_price, supplier_id, margin, internal notes. Mark them private and they stay available to trusted callers only.

Marking fields private

Index → Search settings → Private fields. The list is stored in settings.search.privateFields (at most 64 fields). Saving checks that every field exists in the schema and that the index's default query_by, facet_by, sort_by and highlight settings do not use a private field — those defaults apply to every unprivileged search, so the save is refused with PRIVATE_FIELD instead. Search profiles are checked the same way when they are saved.

Who sees private fields

CallerPrivate fields
API key with documents:read_private (or *)Yes
API key without that scopeNo
Embed widget (/embed/*)Never — the token is public by construction
Assistant, answer, RAGNever — the model context must not see them
GET /documents/exportOnly with documents:read_private; otherwise every line is stripped
Snapshot download (GET /indexes/{slug}/snapshots/{id})Only with documents:read_private; otherwise download is null
Scoped keys (POST /indexes/{slug}/scoped-key)Not issued at all for an index with private fields unless the issuing key has documents:read_private (400 PRIVATE_FIELDS_PRESENT): a browser holding the key could filter on a private field and read found
ConsoleOrganization owners and admins yes, members no (documents opened by a member arrive without them)

What is removed

For a caller without access, a private field disappears from every place a value can surface: the document in each hit, highlights, text-match info, facet counts, grouped hits, explain payloads, GET /documents/{id} and the export. Rerankers never receive private fields for an unprivileged call.

Referencing a private field is an error

Stripping the value is not enough: a filter such as cost_price:<10 would still reveal the value by what matches. A request that names a private field in query_by, filter_by, sort_by, facet_by, include_fields or group_by without the scope is rejected:

curl -sS "$AAC_API/indexes/products/search?q=drill&filter_by=cost_price:<10" \
  -H "Authorization: Bearer $AAC_KEY"
{
	"error": {
		"code": "PRIVATE_FIELD",
		"message": "This request references private fields (cost_price); a key with the documents:read_private scope is required",
		"details": { "fields": ["cost_price"], "params": ["filter_by"] },
		"request_id": "req_…"
	}
}

The status is 400. POST /embed/search answers the same 400 when a search names a private field.

The check runs on what the engine would receive: facetFilters are merged into filter_by first, and field names are compared after trimming quotes, spaces and ! — " cost_price:10" is refused like cost_price:10. The facet-value search (/facets/{attribute}/search) and /assistant/native apply the same rule.

Granting the scope

Create a server-side key with documents:read_private in addition to search:read. Never use such a key in a browser.

On this page