Permissions and roles#
The system checks on every request whether that identity may perform that action on that resource.
Roles#
| Role | Who |
|---|---|
guest |
not signed in |
registrated |
a signed-in user |
owner |
the owner of a particular record — a conditional role |
receiver |
the receiver of a particular record — a conditional role |
admin |
an administrator |
Roles allowed per application#
The roles that take effect on a call are not up to the user alone. The website administrator
chooses which roles the application may use (in Settings → Applications with Api access) —
a ceiling above the user's rights. The key itself adds no rights. An application without a
token acts as guest (see Authentication); with a token the user's roles
are used, but only from the allowed set.
The guest role is always allowed — the administrator cannot remove it. A new application
gets this default: guest, registrated and the conditional roles owner
(own records) and receiver (records addressed to the user). Administrator (privileged) roles
only if the administrator explicitly ticks them. Partner integrations usually get the default
roles; the website owner's own mobile app may get all of them.
Ask the website administrator which roles your application has. They cannot be found out from outside.
An administrator through an application without the admin role is a regular user
If an administrator signs in through an application that does not allow the admin
role, the API treats them as a regular registered user: admin-only actions are refused
(403), and admin-only fields and unmasked personal data of other users are not returned.
Without owner, users cannot edit even their own records
An application that does not allow owner cannot edit even the records of its signed-in
user.
The guest role is always allowed
Calls without a token and public actions (sign-in, token refresh, forgotten password) are
evaluated with the guest role. Without it a user could not even sign in through the
application, so every application always has it allowed and it cannot be removed.
The model is flat#
A missing rule means denial
There is no inheritance and no fallback. If there is no record for a given resource-and-action pair, the action is denied — administrators included, until the rule is added.
A rule key looks like:
For example :Api:Eshop:Order:updateColumns.
What is public#
Mainly reading is open to visitors who are not signed in (calls with an application key but without a token): articles, pages, code lists, categories and products.
Writing always requires signing in.
The exact list is in the data, not in the code
Which rules are open is a setting of the particular installation. The quickest way to find
out is to call the action with the application key alone, without a token — it either passes
or you get Permission denied.
Ownership#
The owner role permits nothing by itself. Ownership is verified against the
database.
Ownership is never taken from the request
If you send your own user identifier in the data, the system ignores it and on insert fills the owner in from the server.
The receiver#
Next to owner ("the record is mine") there is a second conditional role, receiver ("the
record was addressed to me"). It is a separate role: where a record has an author and an
addressee, the addressee need not hide under the owner.
owner |
receiver |
|
|---|---|---|
| It asks | is the caller the creator or sender of the record? | is the caller the one the record was addressed to? |
| Example | the author of a rating, the sender of a reply to an advert | the advert's owner for a reply to it; the owner of a rated profile for a reply to a rating |
Like owner it allows nothing by itself. A rule with the role receiver passes only
when the check of the given area confirms it against the database. Who the receiver is is
never taken from the request.
An unconditional rule wins
If there is an unconditional rule for the same key (say, for registrated), every
signed-in user passes and the owner or receiver condition is never evaluated. To
limit a right to the owner or receiver there must be no unconditional rule for another
role.
Where receiver is used today#
| Resource and action | The receiver is |
|---|---|
response (classifieds and company directory) — get |
the advert's owner, the holder of the advert password, an administrator; not the author of the reply |
rating — save-reply |
the owner of the rated profile; the author of the rating may not reply |
For a reply to an advert (response) also: save (edit) and delete stay with the role
owner, which both the sender of the reply and the advert's owner satisfy. Writing the
read column (marking as read) is allowed by the receiver check; other update-columns
columns are for an administrator only.
Marking from the email does not pass from outside
The Označit jako přečtené (mark as read) button in the email uses a key stored with the reply. It is verified only on the internal path (the site calls the API directly) and is not passed in an HTTP request. If you send the key in the address of an external call it does not help — only signing in (or the advert password) makes you the receiver.
Private messages use owner and receiver on conversations, but without HTTP
Actions on one conversation (read messages, reply, mark read, hide, list participants)
use both roles: owner is the one who wrote at least one message in the conversation,
receiver the one to whom a message came. These actions are internal — they have
no HTTP wrapper and are unavailable from outside. Over HTTP the whole resource is for an
administrator only.
🔴 Ownership does not work on collections#
get-all, get-by and bulk deletion are always denied to an owner
You cannot say "give me my records" through a general listing. Dedicated actions exist for your own data — but many of them have no HTTP wrapper and are therefore unreachable from outside. See the CRUD pattern.
What you see of other people's records#
The permission for an action decides whether you reach the record. Which values the response
gives you is decided by the field itself as well — even when the entity arrives as a
nested relation through includes.
Other users' personal data#
The record owner and an administrator see everything. Everyone else gets other people's
personal data according to the installation setting (security.personalDataExposure,
default sanitized):
| Data | Default for another caller |
|---|---|
masked (pet***@p***.cz) |
|
| a user's phone, street, postcode, company ID, VAT ID, company name, "about me" | empty string |
| IP addresses (on adverts, responses, posts, votes, payments, orders…) | empty string |
| a user's credits, points, discount and discount level | the key is missing from the response |
The full mode returns everything, the empty mode empties the e-mail too. Which field is
personal data is defined in the code; the operator chooses the mode.
Read a seller's contact from the public profile
What a visitor is meant to see is returned by user/public-profile, and the phone on
click by user/show-phone (rate limited). Do not rely on includes=user.
A missing key is not an error
A hidden numeric field is dropped from the row, because an empty string would not be a number. Your parser has to expect it.
Fields that never leave the server#
Some values are needed only by the website inside the server, and nobody gets them over
HTTP, not even an administrator with a token: the advert and post password, the activation
key of an advert and of a newsletter subscription, the readKey of an advert response, the
cart key, the outgoing mail password and the hashes of tokens and API keys. A user's password
and salt and one-time tokens (password reset, account deletion) are not read even by the
website.
In a response such fields are missing or carry a default value.
Filters and ordering only over fields you may read#
A filter is as good a channel as a response: whoever may filter by a password or an e-mail
recovers it character by character. That is why filters (nested groups included),
orderBys and columns may target only fields you would also get in the response.
| Path | Result for a regular user |
|---|---|
user.id, orderBys[user.username] |
passes |
user.password, user.salt, activationKey |
HTTP 400 |
user.email, insertIp, user.phone (personal data) |
HTTP 400 |
| a non-existent field (a typo) | HTTP 400 |
{"success":false,"message":"Field path \"user.email\" is not allowed in filters, orderBys or columns.","data":null,"code":2}
An administrator filters without restriction.
Fields you may write#
Writing follows the same logic as reading: besides the right to the action, the field
decides. A field your account may not write is silently dropped from data and the
original value stays in the database (on insert, the default). An owner thus cannot change
the credits, discount or e-mail of their account, nor move their comment or response to
someone else's record. The list for your own account is in the
CRUD pattern.
Modes#
| Mode | Behaviour |
|---|---|
off |
no checking |
log |
evaluates and records, denies nothing |
enforce |
denies — the current state |
The switch applies to the whole API at once
There is no per-module switch. Expect that permission behaviour may change across the board.
A denial#
with HTTP status 403.
Code 400 means permission denied, not “bad request”
The numeric codes look strikingly like HTTP statuses but do not correspond to them. See Error codes.
Calls from inside the system#
The website and administration call the same resources through an internal route — without HTTP, by building the request directly, so they need no application key. That does not bypass the permission check: both routes go through the same guard and merely have a switch of their own.
For you as an external client, one conclusion follows
Behaviour you see on the website may not be reachable through the API — not because the website skips the check, but because many actions have no HTTP wrapper. See The CRUD pattern.
The difference between the website and the API is not permissions, but which actions exist
If something the website does will not work through the API, look for a missing endpoint or a missing rule — not for a "website exception".