Reference
Roles
Roles are defined once per environment and assigned per membership. Permissions travel inside the session, so checking one is a local array lookup rather than a network call — which is the whole point: an authorization check that costs a round trip is an authorization check somebody will eventually skip.
The role object
A role is a named bundle of permission slugs. Your code should check permissions, not roles: a check written against invoices:write survives the day a customer asks for a Finance role that is Admin without member management, and a check written against role === admin does not.
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier, prefixed role_. |
| object | string | Always "role". |
| name | string | Human-readable name. Shown to customers in the Admin Portal. |
| slug | string | Stable machine identifier, lower-case with hyphens. This is what appears in a session; never rename it once code depends on it. |
| description | string | null | One line explaining the role to whoever assigns it. |
| type | string | EnvironmentRole for a role you define, or OrganizationRole for one a customer defined for their own tenant. |
| permissions | array | Permission slugs granted by the role. Keep them resource:action shaped so the list stays readable at fifty entries. |
| created_at | timestamp | RFC 3339. |
| updated_at | timestamp | RFC 3339. |
Slugs are an interface
Once a slug appears in a session it appears in your code, in your customers' scripts and in their runbooks. Renaming one is a breaking change even though nothing in this API stops you. Change the name field instead — it exists for exactly this reason.
Roles and memberships
A membership joins one user to one organization and carries exactly one role. That constraint is what keeps authorization answerable: given a user id and an organization id there is one role, one permission set, and no union to compute at request time.
| Field | Type | Description |
|---|---|---|
| id | string | Prefixed om_. The join between one user and one organization. |
| user_id | string | The person. |
| organization_id | string | The tenant. |
| role | object | The assigned role, returned as a slug. Exactly one role per membership. |
| status | string | active, inactive or pending. A pending membership is an invitation not yet accepted. |
Endpoints
Paths are relative to https://api.paycux.com. See the reference overview for authentication.
| Method | Path | What it does |
|---|---|---|
| GET | /roles | List the roles defined in this environment. |
| POST | /roles | Create a role with a slug and a set of permissions. |
| GET | /roles/:id | Retrieve a single role with its full permission list. |
| PUT | /roles/:id | Change a role's name, description or permission set. |
| DELETE | /roles/:id | Delete a role that is no longer assigned to anybody. |
| PUT | /memberships/:id | Change the role held by one person in one organization. |
List roles
Roles are environment-scoped, which means the same set applies to every organization unless a customer has defined their own. Read this list to render a role picker rather than hard-coding the options in your interface.
1curl https://api.paycux.com/roles \2 -H "Authorization: Bearer sk_example_123456789"
1HTTP 2002{3 "object": "list",4 "data": [5 {6 "object": "role",7 "id": "role_01HQ8ZK3M4N5P6R7S8T9V0W1X2",8 "name": "Admin",9 "slug": "admin",10 "description": "Full access, including billing and member management",11 "type": "EnvironmentRole",12 "permissions": ["members:manage", "invoices:read", "invoices:write", "settings:write"],13 "created_at": "2026-03-02T08:00:00.000Z",14 "updated_at": "2026-03-02T08:00:00.000Z"15 },16 {17 "object": "role",18 "id": "role_01HQ8ZK3M4N5P6R7S8T9V0W1X3",19 "name": "Member",20 "slug": "member",21 "description": "Everyday access with no administrative rights",22 "type": "EnvironmentRole",23 "permissions": ["invoices:read"],24 "created_at": "2026-03-02T08:00:00.000Z",25 "updated_at": "2026-03-02T08:00:00.000Z"26 }27 ],28 "list_metadata": { "before": null, "after": null }29}
Create a role
Start with three roles and add the fourth when a customer asks for it by name. A permission list is easy to extend; a role taxonomy invented in advance is hard to retire, because slugs leak into code, into scripts, and into the customer's own documentation.
1curl -X POST https://api.paycux.com/roles \2 -H "Authorization: Bearer sk_example_123456789" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "Billing admin",6 "slug": "billing-admin",7 "description": "Can manage invoices and payment methods, nothing else",8 "permissions": ["invoices:read", "invoices:write", "payment-methods:write"]9 }'
1HTTP 2012{3 "object": "role",4 "id": "role_01HQ8ZK3M4N5P6R7S8T9V0W1X4",5 "name": "Billing admin",6 "slug": "billing-admin",7 "description": "Can manage invoices and payment methods, nothing else",8 "type": "EnvironmentRole",9 "permissions": ["invoices:read", "invoices:write", "payment-methods:write"],10 "created_at": "2026-05-02T11:41:09.006Z",11 "updated_at": "2026-05-02T11:41:09.006Z"12}
Retrieve a role
Useful on an admin screen that explains what a role grants before somebody assigns it. The permission list is authoritative here; a session carries the same set, but only as of the moment it was issued.
1curl https://api.paycux.com/roles/role_01HQ8ZK3M4N5P6R7S8T9V0W1X4 \2 -H "Authorization: Bearer sk_example_123456789"
1HTTP 2002{3 "object": "role",4 "id": "role_01HQ8ZK3M4N5P6R7S8T9V0W1X4",5 "name": "Billing admin",6 "slug": "billing-admin",7 "description": "Can manage invoices and payment methods, nothing else",8 "type": "EnvironmentRole",9 "permissions": ["invoices:read", "invoices:write", "payment-methods:write"],10 "created_at": "2026-05-02T11:41:09.006Z",11 "updated_at": "2026-05-02T11:41:09.006Z"12}
Update a role
The permissions array replaces the previous one rather than merging into it. Existing sessions keep the permissions they were issued with until they refresh, so a permission you remove here is not gone from every browser the instant this call returns.
1curl -X PUT https://api.paycux.com/roles/role_01HQ8ZK3M4N5P6R7S8T9V0W1X4 \2 -H "Authorization: Bearer sk_example_123456789" \3 -H "Content-Type: application/json" \4 -d '{5 "permissions": ["invoices:read", "invoices:write"]6 }'
1HTTP 2002{3 "object": "role",4 "id": "role_01HQ8ZK3M4N5P6R7S8T9V0W1X4",5 "name": "Billing admin",6 "slug": "billing-admin",7 "permissions": ["invoices:read", "invoices:write"],8 "updated_at": "2026-06-21T09:12:38.441Z"9}
Delete a role
A role still held by a membership cannot be deleted; the call returns 409 with the number of memberships in the way. Reassign those people first, which is deliberate friction — deleting a role out from under a live session is a good way to lock a customer's admin out of their own account.
1curl -X DELETE https://api.paycux.com/roles/role_01HQ8ZK3M4N5P6R7S8T9V0W1X4 \2 -H "Authorization: Bearer sk_example_123456789"
1HTTP 4092{3 "code": "role_in_use",4 "message": "This role is assigned to 12 memberships and cannot be deleted.",5 "request_id": "req_01HQ8ZK3M4N5P6R7S8T9V0W1X2"6}
Assign a role to a membership
Roles are assigned per membership, not per user, so this is the call that grants somebody admin rights in one tenant while leaving them an ordinary member in another. The change takes effect for that person at their next token refresh.
1curl -X PUT https://api.paycux.com/memberships/om_01HQ8ZK3M4N5P6R7S8T9V0W1X2 \2 -H "Authorization: Bearer sk_example_123456789" \3 -H "Content-Type: application/json" \4 -d '{ "role_slug": "billing-admin" }'
1HTTP 2002{3 "object": "organization_membership",4 "id": "om_01HQ8ZK3M4N5P6R7S8T9V0W1X2",5 "user_id": "user_01HQ8ZK3M4N5P6R7S8T9V0W1X2",6 "organization_id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",7 "role": { "slug": "billing-admin" },8 "status": "active",9 "updated_at": "2026-06-21T09:15:02.118Z"10}
Errors
A duplicate slug returns 422. Deleting a role that is still assigned returns 409 with the membership count. Assigning a slug that does not exist in the environment returns 422 rather than creating it, so a typo fails loudly instead of granting nothing.
Pagination
GET /roles is cursor paginated, though most environments have fewer than a dozen roles and never see a second page. Membership lists, which do grow, are paginated on the organizations resource.
1// Yetki kontrolu oturumdan okunur — ag cagrisi yok.2function can(session, permission) {3 return session.permissions.includes(permission);4}56if (!can(session, 'invoices:write')) {7 return new Response('Forbidden', { status: 403 });8}