Permissions
Permissions define what each role can do with a resource. Every permission ties
a role to an action (create, read, update, or delete) and specifies up
to three layers of access control: fields, filters, and checks.
The Three-Layer Model
Fields
The fields array lists the field names a role is allowed to interact with for
the given action. Only the specified fields are accepted in request bodies (for
writes) or returned in responses (for reads).
{
"role": "viewer",
"action": "read",
"fields": ["id", "title", "body", "created_at"]
}
System fields (id, created_at, updated_at) are always accessible and do
not need to be explicitly listed.
Filters
The filters array applies WHERE-clause constraints that limit which records a
role can access. Filters are evaluated at query time and are primarily used for
read operations.
{
"role": "user",
"action": "read",
"filters": [
{ "field": "owner_id", "operator": "eq", "claim": "id" },
{ "field": "status", "operator": "neq", "value": "archived" }
]
}
In this example, the user can only read records they own that are not archived.
Checks
The checks array applies validation constraints evaluated at write time. They
are used for create, update, and delete actions to enforce business rules
and enable automatic value injection.
{
"role": "user",
"action": "create",
"fields": ["title", "body"],
"checks": [
{ "field": "owner_id", "operator": "eq", "claim": "id" }
]
}
When a check uses the eq operator with a claim or value, the target field
becomes optional in the request body and its value is automatically injected.
See Claims-Based Auto-Injection below.
Constraint Operators
Constraints used in filters and checks support 27 operators grouped by type:
Comparison
| Operator | Description |
|---|---|
eq |
Equal |
neq |
Not equal |
gt |
Greater than |
lt |
Less than |
gte |
Greater than or equal to |
lte |
Less than or equal to |
Membership
| Operator | Description |
|---|---|
in |
Value in array |
nin |
Value not in array |
is_null |
Field is null |
is_not_null |
Field is not null |
String
| Operator | Description |
|---|---|
like |
SQL LIKE pattern matching |
ilike |
Case-insensitive LIKE |
similar |
SQL SIMILAR TO pattern |
regex |
Regular expression match |
iregex |
Case-insensitive regular expression |
JSON
| Operator | Description |
|---|---|
contains |
JSON contains key/value |
contained_in |
Value is contained in JSON |
has_key |
JSON object has specific key |
has_keys_any |
JSON has any of the given keys |
has_keys_all |
JSON has all of the given keys |
Relationship
| Operator | Description |
|---|---|
exists |
Related record exists |
not_exists |
Related record does not exist |
Logical
| Operator | Description |
|---|---|
_and |
Logical AND |
_or |
Logical OR |
_not |
Logical NOT |
Each constraint object has the following structure:
{
"field": "owner_id",
"operator": "eq",
"value": "some-static-value",
"claim": "id"
}
Use value for static comparisons and claim for dynamic values sourced from
the authenticated user.
Claims-Based Auto-Injection
When a check constraint uses the eq operator with either a claim or a
value, Snaapi automatically handles the target field:
- The field becomes optional in the request schema — callers do not need to provide it.
- At request time, the value is injected automatically from the claim source or static value.
- If the caller provides a value for the field, it is overridden by the
injected value. This prevents tampering — users cannot set their own
owner_id, for example.
Claim Sources
Claims reference fields from the authenticated user object using dot notation:
| Claim Example | Resolves To |
|---|---|
id |
The user's ID |
email |
The user's email address |
metadata.team_id |
A nested value in user metadata |
Example
Given this permission:
{
"role": "user",
"action": "create",
"fields": ["title", "body"],
"checks": [
{ "field": "owner_id", "operator": "eq", "claim": "id" }
]
}
A user creating a post only needs to provide title and body. The owner_id
field is automatically set to the authenticated user's ID, and any user-supplied
owner_id value is discarded.
Admin Token Restrictions
When an admin authenticates via an API key (admin token), resource endpoints restrict write operations:
| Operation | Allowed |
|---|---|
GET (read) |
yes |
DELETE |
yes |
POST (create) |
no |
PUT/PATCH (update) |
no |
Create and update requests return a 403 response with error code
ADMIN_TOKEN_NOT_ALLOWED. Use the dedicated admin endpoints for these
operations instead.
This restriction ensures that write operations by admin tokens go through dedicated admin endpoints with proper audit trails.