A valid request URL is required to generate request examples{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"key": "teacher",
"name": "Professor",
"is_system": true,
"permissions": [
"$team.manage",
"grades:write"
],
"current_version": 123,
"member_count": 123
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "role_exists",
"message": "a role with this key already exists in this environment"
}
}Create a customer role
Requires customers:write. The key is the stored literal: it is what a membership names, what the customer_role JWT claim carries, and what your own code branches on. It cannot be changed afterwards — renaming one would break your gate with no error raised anywhere on our side — so a rename is a new role plus a reassignment, which is two calls you can see yourself making.
A permission beginning with $ must be one of the reserved capabilities; anything else in that namespace is refused rather than stored, because a role that looks like it grants something and grants nothing is discovered by whoever it locked out. Unprefixed permissions are yours and are only checked for shape.
Keys are unique per environment; a duplicate answers 409 role_exists.
A valid request URL is required to generate request examples{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"key": "teacher",
"name": "Professor",
"is_system": true,
"permissions": [
"$team.manage",
"grades:write"
],
"current_version": 123,
"member_count": 123
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "role_exists",
"message": "a role with this key already exists in this environment"
}
}Authorizations
A staff session token, uk_st_…. Minted by sign-up, sign-in or the two-factor exchange. Only a 401 means it is spent; not_a_member (403) is about the organization named in X-Organization-Id and leaves the token good for the others.
Headers
The organization the caller is acting on — the org_… code that appears in the panel URL. It identifies; the membership JOIN is what authorizes, so a forged code reads nothing: the answer is not_a_member (403), which does not mean the session is over. Absent, the session's default organization answers, or — if that membership was revoked while the session was open — any other one the caller still holds.
Query Parameters
Which environment to act in. A view parameter, valid only on the staff surface — a machine credential never chooses its environment, it is resolved from the key.
live, test Body
The stored literal your code branches on. Immutable once created.
^[a-z0-9_-]{1,32}$"teacher"
The display string. This one is editable.
80"Professor"
Reserved $… capabilities and your own strings, in one list.
["$team.manage", "grades:write"]
Response
The role.
A role inside a customer, in one environment. permissions mixes the two vocabularies: $… entries are the capabilities UserKit evaluates, everything else is yours and reaches your backend through the contact's JWT.
The stored literal. Immutable.
"teacher"
"Professor"
owner and member, seeded into every environment. Not deletable; name and permissions still editable.
["$team.manage", "grades:write"]
Which version of the permission set is live. An edit bumps it; a restore moves it back.
Memberships holding this role. A role with any cannot be deleted.