Control which rows and columns each user can see, with row-level and column-level security policies.
Project availability: ✅ Classic projects • ✅ Agentic projects
Wren AI's data security controls which rows and columns each user can see in their answers. The Data Security API manages the same settings as the Data Security page in Wren AI, so you can keep access rules in version control and apply them to another project the same way every time.
Data security is made up of three building blocks:
- Session Properties: named values such as
regionorrolethat describe a user - Policies: row-level security (RLS) policies filter rows, and column-level security (CLS) policies hide columns, based on a user's session properties
- Assigned Values: the value of each session property for a project member or a group
Endpoints Overview
Session Properties
GET /v2/projects/{projectId}/data_security/session_properties: List session properties.POST /v2/projects/{projectId}/data_security/session_properties: Create a session property.GET /v2/projects/{projectId}/data_security/session_properties/{id}: Get a session property.PATCH /v2/projects/{projectId}/data_security/session_properties/{id}: Update a session property's description.DELETE /v2/projects/{projectId}/data_security/session_properties/{id}: Delete a session property and its assigned values.
Policies
GET /v2/projects/{projectId}/data_security/policies: List RLS and CLS policies.POST /v2/projects/{projectId}/data_security/policies: Create a policy.GET /v2/projects/{projectId}/data_security/policies/{id}: Get a policy.PATCH /v2/projects/{projectId}/data_security/policies/{id}: Update a policy.DELETE /v2/projects/{projectId}/data_security/policies/{id}: Delete a policy.
Assigned Values
GET /v2/projects/{projectId}/data_security/member_values: List the values assigned to members.PATCH /v2/projects/{projectId}/data_security/member_values: Set values for up to 500 members.GET /v2/projects/{projectId}/data_security/group_values: List the values assigned to groups.PATCH /v2/projects/{projectId}/data_security/group_values: Set values for up to 500 groups.GET /v2/projects/{projectId}/data_security/effective_values?email=: Show the values that apply to one user, and where each comes from.
Export and Import
GET /v2/projects/{projectId}/data_security: Export the whole setup as one document.POST /v2/projects/{projectId}/data_security/import: Apply an export to a project, as a dry run by default.
The Data Security API requires the Enterprise plan. On other plans every endpoint returns 403 PLAN_NOT_SUPPORTED. Reading works with a project API key or an organization API key.
Changes need an organization API key
Creating, updating and deleting requires an organization API key. A project API key gets
403 ACTION_FORBIDDEN.
Basic Usage
This example creates a region property, limits orders and accounts to EMEA rows, and assigns a value to one member.
Create a Session Property
Session property names use letters, numbers and underscores.
Request — POST /v2/projects/1/data_security/session_properties
{
"name": "region",
"type": "STRING",
"description": "Sales region"
}Response
{
"id": 12,
"name": "region",
"type": "STRING",
"description": "Sales region",
"usedByPolicies": [],
"createdAt": "2026-10-16T08:00:00.000Z",
"updatedAt": "2026-10-16T08:00:00.000Z"
}Create an RLS Policy
The condition refers to the session property as @region.
Request — POST /v2/projects/1/data_security/policies
{
"name": "emea_accounts_only",
"type": "RLS",
"models": ["orders", "accounts"],
"condition": "@region = 'EMEA'",
"sessionProperties": [
{ "name": "region", "isRequired": true }
]
}Create a CLS Policy
A CLS policy uses one session property. In this example, users whose role is not finance cannot see gross_margin.
Request — POST /v2/projects/1/data_security/policies
{
"name": "hide_margin",
"type": "CLS",
"columns": [{ "model": "orders", "column": "gross_margin" }],
"sessionProperties": [
{
"name": "role",
"isRequired": false,
"defaultValue": "viewer",
"operator": "EQUALS",
"comparisonValue": "finance"
}
]
}Assign Values
null clears a value, and properties you leave out stay as they are.
Request — PATCH /v2/projects/1/data_security/member_values
{
"members": [
{ "email": "[email protected]", "properties": { "region": "EMEA" } },
{ "email": "[email protected]", "properties": { "region": null } }
]
}Copying a Setup to Another Project
Policies refer to models, columns and session properties by name, so an export from one project works on any project with the same models. This is how a setup moves from QA to production.
- Export the source project with
GET /data_security. - Send the export to the target project's
POST /data_security/importand review the plan. - Send it again with
"dryRun": falseto apply it.
See Import data security setup for the details.
When Changes Apply
Creating, updating or deleting a policy redeploys the project before the request returns. A failed redeploy is not reported on these endpoints, so deploy the project again if a change does not take effect. Import reports it in deploy.status. Assigned values apply to users signed in to Wren AI. Requests made with an API key pass session property values in the X-Wren-Session-Properties header instead.
Errors
Errors return a code and an error message. Some codes also include fields that name what went wrong, such as models on UNKNOWN_MODEL or path on INVALID_INPUT.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_INPUT | An id or a field in the request is invalid, or the body is not valid JSON. |
| 400 | INVALID_POLICY | A policy is missing a required field. |
| 400 | UNKNOWN_MODEL / UNKNOWN_COLUMN / UNKNOWN_SESSION_PROPERTY | A name in the request does not exist in the project. |
| 400 | INVALID_CONDITION | The RLS condition is not valid. error includes the engine's reason. |
| 400 | COLUMN_ALREADY_PROTECTED | Another CLS policy already protects the column. |
| 400 | INVALID_PROPERTY_VALUE | A value does not match its session property's type. |
| 400 | IMMUTABLE_FIELD | The request changes a field that cannot be changed. |
| 400 | TOO_MANY_ENTRIES | The request has too many entries. |
| 403 | PLAN_NOT_SUPPORTED | The organization is not on the Enterprise plan. |
| 403 | ACTION_FORBIDDEN | The action needs an organization API key. |
| 404 | RESOURCE_NOT_FOUND | The project, or the item in this project, does not exist. |
| 404 | NOT_A_MEMBER | An email or group is not in the project. |
| 405 | METHOD_NOT_ALLOWED | The endpoint does not support the HTTP method. |
| 409 | DUPLICATED_SESSION_PROPERTY_NAME / DUPLICATED_POLICY_NAME | The name is already used. |
| 409 | IN_USE | A policy still uses the session property. |
| 409 | TYPE_MISMATCH | On import, an item in the file has a different type from the one in the project. |
| 409 | AMBIGUOUS_MEMBER | An email matches more than one user. |
| 409 | PROJECT_NOT_DEPLOYED | The project has not been deployed yet. Deploy it before adding policies or importing. |
| 413 | INVALID_INPUT | The request body is larger than 1 MB, or 10 MB on import. |
How to create organization API keys
Visit Organization API Keys to learn more.
