Appearance
Team & roles API
Invite people to your account and control what they can do with roles. A role is a named bundle of permissions; you assign one to each member (and, with the same model, to API keys).
All endpoints need a bearer token. In practice team, role, and account management is Owner work done from a login session - API keys can never hold team, roles, or account permissions, so these endpoints always return 403 to a key.
For the concept-level overview, see Team & roles.
Owner vs member
- The Owner is whoever registered the account. They hold the global wildcard
*- full access to everything, implicitly. There is no role to assign and nothing to configure, and the wildcard is never stored on a role. - Everyone else is a Member with exactly one assigned role. Their access is whatever that role grants, and nothing more.
You can never grant more than you hold yourself. Creating, editing, or assigning a role whose permissions exceed your own returns 403 ("A role cannot grant permissions beyond your own"). The Owner is the only account holder this never bites.
Permission tokens
A permission is a resource:action string, for example pods:read or billing:pay. A wildcard resource:* grants every action on that resource (pods:*). Roles are built from these tokens. The global * is not grantable - it belongs to the Owner alone.
A few actions are more sensitive than the rest - grant them deliberately. They are flagged "sensitive": true in the catalogue:
pods:console- open a shell inside a poddomains:ssl- issue and manage SSL certificatesbilling:pay- spend money (top up credit)
The permission catalogue
http
GET /api/permissionsNeeds roles:read. Returns every resource and the actions it supports - the source of truth for what you can put in a role. Each action is an object, not a bare string:
json
{
"resources": [
{
"key": "pods",
"label": "Pods",
"actions": [
{ "key": "read", "label": "View", "sensitive": false },
{ "key": "create", "label": "Create", "sensitive": false },
{ "key": "update", "label": "Update", "sensitive": false },
{ "key": "delete", "label": "Delete", "sensitive": false },
{ "key": "console", "label": "Console access", "sensitive": true }
]
}
]
}Build a token by joining the two keys with a colon: pods + console gives pods:console. The array is in display order, so you can render it straight into a permission grid.
The 16 resources and their actions:
| Resource | label | Actions |
|---|---|---|
pods | Pods | read, create, update, delete, console |
networks | Networks | read, create, update, delete |
firewalls | Firewalls | read, create, update, delete |
domains | Domains | read, create, update, delete, ssl |
snapshots | Snapshots | read, create, update, delete |
backups | Backups | read, create, update, delete |
email | read, create, update, delete | |
sshkeys | SSH keys | read, create, delete |
monitoring | Monitoring | read, update |
billing | Billing | read, pay |
support | Support | read, create, update |
apikeys | API keys | read, create, delete |
roles | Roles | read, create, update, delete |
team | Team members | read, create, update, delete |
account | Account settings | read, update |
audit | Activity log | read |
Not every resource has all four verbs. sshkeys and apikeys have no update because there is nothing to edit - delete and re-add. audit is read-only, and monitoring has no create or delete.
Roles
List roles
http
GET /api/roles/Needs roles:read. Returns your account's roles as a plain array, seeding the four system roles on first call if they are not there yet. A role looks like:
json
{
"id": "6a4d5b83b7006ccae6391841",
"accountId": "6a4d5b83b7006ccae6391800",
"name": "Deployer",
"description": "Can manage pods and domains",
"permissions": ["pods:*", "domains:read", "domains:update"],
"isSystem": false,
"createdAt": "2026-07-16T09:41:02Z",
"updatedAt": null
}System roles
Every account starts with four ready-made roles. They are marked isSystem: true and cannot be edited or deleted:
| Role | Grants |
|---|---|
| Full access | Everything except team, roles, and account settings. The Owner always has those; a custom role can grant them too if you want to delegate. |
| Developer | Manage pods, networks, firewalls, domains, snapshots, backups, SSH keys, monitoring, and support; read-only email and activity log. |
| Read-only | The read action on everything. Look, don't touch. |
| Billing | Full billing; read-only account and activity log. |
Their permission lists are expanded from the catalogue rather than written out, so a resource added to the platform flows into them automatically.
Create a role
http
POST /api/roles/Needs roles:create.
json
{
"name": "Deployer",
"description": "Can manage pods and domains",
"permissions": ["pods:*", "domains:read", "domains:update"]
}Returns 201 with the new role. Duplicate tokens are de-duplicated for you.
Update a role
http
PUT /api/roles/:roleIdNeeds roles:update.
json
{
"name": "Deployer",
"description": "Pods, domains, and now snapshots",
"permissions": ["pods:*", "domains:*", "snapshots:*"]
}Only your own custom roles - a system role returns 403 ("System roles cannot be edited"). Every field is replaced, so send the full permissions list, not a delta.
Editing a role takes effect immediately: everyone holding it is re-evaluated on their next request, and a permission you removed stops working right away rather than at the end of a cache window.
Delete a role
http
DELETE /api/roles/:roleIdNeeds roles:delete.
json
{ "message": "Role deleted" }| Problem | Status | Message |
|---|---|---|
| Role is a system role | 403 | System roles cannot be deleted |
| Role is still assigned to someone | 409 | This role is still assigned to team members. Reassign them before deleting it. |
Unknown token in permissions | 400 | Unknown or disallowed permission: pods:deploy |
Empty name | 400 | Role name is required |
Team members
List members
http
GET /api/team/Needs team:read. A plain array of users on the account, including you. The Owner's entry has accountRole: "owner" and no accountRoleId:
json
[
{
"id": "6a4d5b83b7006ccae6391802",
"username": "bob",
"firstName": "Bob",
"lastName": "Lee",
"email": "[email protected]",
"lastLogin": null,
"isActive": true,
"isVerified": true,
"role": "user",
"accountRole": "member",
"accountRoleId": "6a4d5b83b7006ccae6391841",
"accountId": "6a4d5b83b7006ccae6391800",
"createdAt": "2026-08-02T14:30:00Z",
"updatedAt": null
}
]An invited member is created verified (you vouched for them) with lastLogin: null until they set a password and sign in.
Invite a member
http
POST /api/team/Needs team:create.
json
{
"username": "bob",
"firstName": "Bob",
"lastName": "Lee",
"email": "[email protected]",
"roleId": "6a4d5b83b7006ccae6391841"
}Sends an email invitation. The invited person clicks the link and sets their own password through the password-reset endpoint - you never see or set a temporary one, and the invitation link is good for 7 days. Leave roleId empty and they start on the least-privilege Read-only role, which you can change later. Returns 201 with the new member.
A taken username or email returns 409. A roleId that is not one of your account's roles returns 400.
Change a member's role
http
PATCH /api/team/:userId/roleNeeds team:update.
json
{ "roleId": "6a4d5b83b7006ccae6391841" }json
{ "message": "Member role updated" }The Owner's role cannot be changed (400), and the member's live sessions pick up the new permissions on their next request.
Enable or disable a member
http
PATCH /api/team/:userId/statusNeeds team:update.
json
{ "isActive": false }Returns the updated member. Disabling a member blocks their access without removing them - handy for someone who is away for a while. Their sessions stop working immediately, and so do any API keys they created.
You cannot change your own status, and the Owner cannot be disabled. Both return 400.
Remove a member
http
DELETE /api/team/:userIdNeeds team:delete.
json
{ "message": "Team member removed" }Removes them from the account and revokes any API keys they created. Key revocation happens first: if it fails, the removal is abandoned with a 500 rather than leaving a live key behind an account that no longer exists.
You cannot remove yourself, and the Owner cannot be removed. Both return 400.
Example
Invite a developer, then check what they can do:
bash
curl -X POST https://cloud-api.microapps.io/api/team/ \
-H "Authorization: Bearer $MICROAPPS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "bob",
"firstName": "Bob",
"lastName": "Lee",
"email": "[email protected]",
"roleId": "6a4d5b83b7006ccae6391841"
}'
curl -s https://cloud-api.microapps.io/api/roles/ \
-H "Authorization: Bearer $MICROAPPS_TOKEN" \
| jq '.[] | select(.id == "6a4d5b83b7006ccae6391841") | .permissions'