Filter Registration API
The Filter Registration endpoints allow you to define how user context (access rules, ownership scope, and session filters) maps to metadata filters on each nexset. Once registered, the Agentic RAG endpoint automatically resolves these schemas at query time, converting the caller's user_context into per-nexset access control and pre-retrieval filters.
Base path: /v2/nexsets/{nexset_id}/filters
Authentication
All endpoints require an Authorization header. See the GenAI RAG API overview for full details.
Access is role-gated against the target nexset via the Nexla Admin API:
- Read endpoints (GET) require at least
collaboratorrole on the nexset - Write endpoints (POST, PATCH, DELETE, refresh) require at least
operatorrole on the nexset
Concepts
Filter Types
Each filter entry belongs to one of three layers, which determines how the value is sourced from user_context at query time:
| Type | Source at Query Time | Applied As | Description |
|---|---|---|---|
access_rules | user_context.access_rules | ACL filter | Policy-level gates (e.g., "can this user access confidential docs?") |
access_scope | user_context.access_scope | ACL filter | Data ownership scope (e.g., "only docs from this tenant"). Values use IN semantics |
filters | user_context.filters | Pre-retrieval filter | Page or session context (e.g., "only docs from this project") |
Filters can never expand access beyond what access_rules and access_scope permit. If a key appears in both access_scope and filters, Nexla applies the intersection — filters only narrow, never broaden.
Filter Operators
| Operator | Value Type | Description |
|---|---|---|
EQ | single value | Equals |
NEQ | single value | Not equals |
IN | array | Value is in the provided list |
NOT_IN | array | Value is not in the provided list |
GT | single value | Greater than |
GTE | single value | Greater than or equal |
LT | single value | Less than |
LTE | single value | Less than or equal |
CONTAINS | single value | Field contains the value (substring match) |
NOT_CONTAINS | single value | Field does not contain the value |
EXISTS | (ignored) | Field exists and is not null |
NOT_EXISTS | (ignored) | Field does not exist or is null |
BETWEEN | array of two values [min, max] | Value is within the inclusive range |
Filter Registration Entry
Used in the request body for POST /v2/nexsets/{nexset_id}/filters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | Yes | — | Metadata field name to filter on. Must match ^[\w\-\.]{1,255}$ |
type | string | Yes | — | Filter type: "access_rules", "access_scope", or "filters" |
operator | string | No | "EQ" | Default comparison operator for this filter |
description | string or null | No | null | Human-readable description |
nexset_field | string or null | No | null | Backend field name, if it differs from key. When set, key is used to look up the value in user_context and nexset_field is the actual metadata field or column consulted by the backend |
nexset_field allows decoupling the user_context key name from the actual backend metadata field. For example, you might register key: "tenant_id" with nexset_field: "org_tenant" so that callers use "tenant_id" in user_context while the backend filters on the "org_tenant" column.
Filter Entry Object
All endpoints that return filter data use this object shape:
| Field | Type | Description |
|---|---|---|
id | integer | Unique identifier for this filter entry |
nexset_id | string | The nexset this filter belongs to |
key | string | The metadata field name |
type | string | Filter layer: "access_rules", "access_scope", or "filters" |
operator | string | The comparison operator applied at query time |
description | string or null | Human-readable description |
nexset_field | string or null | Backend field override, if any |
created_at | string | ISO-8601 timestamp of creation |
updated_at | string | ISO-8601 timestamp of last update |
Endpoints
Register Filter Schema
POST /v2/nexsets/{nexset_id}/filters
Register or replace the full filter schema for a nexset. This replaces all existing filters for the nexset.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
nexset_id | string | The nexset identifier. Must match ^[\w\-\.]{1,255}$ |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
filters | array of FilterRegistrationEntry | Yes | The filter entries to register |
Response
Status: 200 OK
{
"nexset_id": "10000",
"filters": [
{
"id": 1,
"nexset_id": "10000",
"key": "tenant_id",
"type": "access_scope",
"operator": "IN",
"description": "Restrict results to the user's tenant",
"nexset_field": null,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
},
{
"id": 2,
"nexset_id": "10000",
"key": "classification",
"type": "access_rules",
"operator": "EQ",
"description": "Document classification level",
"nexset_field": null,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
]
}
Get Filter Schema
GET /v2/nexsets/{nexset_id}/filters
Retrieve the current filter schema for a nexset.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
nexset_id | string | The nexset identifier. Must match ^[\w\-\.]{1,255}$ |
Response
Status: 200 OK
Returns the same envelope as the register endpoint. The filters array is empty when no schema has been registered for the nexset.
Update a Filter Key
PATCH /v2/nexsets/{nexset_id}/filters/{key}?type={type}
Update a single filter key. The type query parameter identifies which filter entry to update when the same key exists under multiple types.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
nexset_id | string | The nexset identifier. Must match ^[\w\-\.]{1,255}$ |
key | string | The filter key name. Must match ^[\w\-\.]{1,255}$ |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The current filter type of the entry to update: "access_rules", "access_scope", or "filters" |
Request Body
All fields are optional. Include only the fields you want to change. At least one must be present.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | No | Move this filter to a different type |
operator | string | No | Update the comparison operator |
description | string | No | Update the description |
nexset_field | string | No | Update the backend field override |
Response
Status: 200 OK
Returns the updated Filter Entry Object.
{
"id": 1,
"nexset_id": "10000",
"key": "tenant_id",
"type": "access_rules",
"operator": "EQ",
"description": "Updated description",
"nexset_field": null,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-16T14:00:00Z"
}
Delete a Filter Key
DELETE /v2/nexsets/{nexset_id}/filters/{key}?type={type}
Remove a single filter key from a nexset.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
nexset_id | string | The nexset identifier. Must match ^[\w\-\.]{1,255}$ |
key | string | The filter key name. Must match ^[\w\-\.]{1,255}$ |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The filter type to delete: "access_rules", "access_scope", or "filters" |
Response
Status: 204 No Content
No response body.
Refresh Filter Cache
POST /v2/nexsets/{nexset_id}/filters/refresh
Invalidates the Redis-backed filter schema cache for the nexset. The next V2 query that needs this nexset's registered filter keys reloads them from Supabase and writes the refreshed schema back to cache.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
nexset_id | string | The nexset identifier. Must match ^[\w\-\.]{1,255}$ |
Response
Status: 200 OK
{
"status": "ok",
"nexset_id": "10000"
}
Error Reference
All error responses use the standard JSON format:
{
"detail": "Error message describing the issue"
}
| Status | Condition | Retryable |
|---|---|---|
| 400 | Invalid nexset_id or key format; PATCH body contains no updatable fields | No |
| 401 | Missing or invalid Authorization header | No |
| 403 | Caller lacks the required role on the nexset (read: collaborator; write: operator) | No |
| 404 | Nexset not found, or filter {key, type} pair not found (PATCH/DELETE) | No |
| 409 | PATCH tries to move an entry to a type where the same key already exists | No |
| 422 | Missing or invalid ?type= query parameter on PATCH/DELETE | No |
| 500 | Internal server error during registration/update/lookup | Maybe |
| 502 | Upstream Admin API error while resolving role for the nexset | Yes |
| 503 | Filter registration backend (Supabase) is not configured on this deployment | No |
Cache Behavior
Nexla caches filter schema per nexset to avoid a database read on every query. The cache is managed as follows:
- POST
/v2/nexsets/{nexset_id}/filters— replaces schema and invalidates cache automatically - PATCH
/v2/nexsets/{nexset_id}/filters/{key}?type={type}— updates key and invalidates cache automatically - DELETE
/v2/nexsets/{nexset_id}/filters/{key}?type={type}— removes key and invalidates cache automatically - POST
/v2/nexsets/{nexset_id}/filters/refresh— explicit cache invalidation, for operational use or after out-of-band changes
If a filter key passed at query time is not registered for a given nexset, Nexla silently skips it for that nexset. In debug mode, unregistered keys are surfaced in intermediate_responses.skipped_filter_keys so the caller can identify mismatches during development.
Examples
Register a Filter Schema
curl -X POST https://api-genai.nexla.io/v2/nexsets/10000/filters \
-H "Content-Type: application/json" \
-H "Authorization: Basic YOUR_API_KEY" \
-d '{
"filters": [
{
"key": "tenant_id",
"type": "access_scope",
"operator": "IN",
"description": "Restrict results to the user tenant"
},
{
"key": "classification",
"type": "access_rules",
"operator": "EQ",
"description": "Document classification level"
},
{
"key": "project_id",
"type": "filters",
"operator": "EQ",
"description": "Page-level project filter"
}
]
}'
Retrieve the Filter Schema
curl https://api-genai.nexla.io/v2/nexsets/10000/filters \
-H "Authorization: Basic YOUR_API_KEY"
Update a Single Filter Key
Change the operator for the tenant_id filter from IN to EQ:
curl -X PATCH "https://api-genai.nexla.io/v2/nexsets/10000/filters/tenant_id?type=access_scope" \
-H "Content-Type: application/json" \
-H "Authorization: Basic YOUR_API_KEY" \
-d '{
"operator": "EQ"
}'
Move a filter key to a different type:
curl -X PATCH "https://api-genai.nexla.io/v2/nexsets/10000/filters/project_id?type=filters" \
-H "Content-Type: application/json" \
-H "Authorization: Basic YOUR_API_KEY" \
-d '{
"type": "access_scope"
}'
Delete a Filter Key
curl -X DELETE "https://api-genai.nexla.io/v2/nexsets/10000/filters/classification?type=access_rules" \
-H "Authorization: Basic YOUR_API_KEY"
Refresh the Filter Cache
curl -X POST https://api-genai.nexla.io/v2/nexsets/10000/filters/refresh \
-H "Authorization: Basic YOUR_API_KEY"
End-to-End: Register Filters Then Query
- Register a filter schema that enforces tenant isolation:
curl -X POST https://api-genai.nexla.io/v2/nexsets/10000/filters \
-H "Content-Type: application/json" \
-H "Authorization: Basic YOUR_API_KEY" \
-d '{
"filters": [
{
"key": "tenant_id",
"type": "access_scope",
"operator": "IN",
"description": "Tenant isolation"
}
]
}'
- Query the Agentic RAG endpoint. The server automatically resolves
user_context.access_scope.tenant_idinto an ACL filter:
curl -X POST https://api-genai.nexla.io/v2/agentic-rag \
-H "Content-Type: application/json" \
-H "Authorization: Basic YOUR_API_KEY" \
-d '{
"user_prompt": "What are the Q2 sales figures?",
"nexsets": ["10000"],
"user_context": {
"user_id": "user-123",
"access_scope": { "tenant_id": ["tenant-1"] }
},
"llm_config": { "credential_id": "cred-456" }
}'
The search results will only include records where tenant_id matches "tenant-1".