Skip to main content

Create or update a user

Creating and updating a user are the same operation and share the same UpsertUserRequest body. POST …/users creates the user — or updates them if the email already exists. PUT …/users/{uid} updates a known user by UID. Both return 200.

Before you start, make sure you have a valid access token — see the Overview for authentication details.

Step 1 — Check whether the user exists

Use the lookup reference to search for the user by email or name. A hit means you will be updating an existing user; a miss means you will be creating a new one.

Step 2 — Gather the UIDs to assign

Collect the group and permission UIDs you want to assign to the user. See the lookup reference for how to retrieve them from the groups and permissions endpoints.

Step 3 — Send the request

Create or upsert by email

POST /v2/organizations/DEMO/users
import requests

BASE_URL = "https://api.stonal.io/users"
TOKEN = "<access_token>"

resp = requests.post(
f"{BASE_URL}/v2/organizations/DEMO/users",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userGroupUids": ["019619df-4768-76b7-81e3-2c56d374df46"],
"permissions": [{"uid": "019619df-4767-730f-8d31-143712a08141"}],
},
)
print(resp.status_code, resp.json())

Update by UID

PUT /v2/organizations/DEMO/users/019619df-4768-76b7-81e3-2c56d374df46
import requests

BASE_URL = "https://api.stonal.io/users"
TOKEN = "<access_token>"

resp = requests.put(
f"{BASE_URL}/v2/organizations/DEMO/users/019619df-4768-76b7-81e3-2c56d374df46",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userGroupUids": ["019619df-4768-76b7-81e3-2c56d374df46"],
"permissions": [{"uid": "019619df-4767-730f-8d31-143712a08141"}],
},
)
print(resp.status_code, resp.json())

Request body

{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"fromExternalIdp": false,
"allAssets": false,
"userGroupUids": ["019619df-4768-76b7-81e3-2c56d374df46"],
"permissions": [
{ "uid": "019619df-4767-730f-8d31-143712a08141" },
{ "uid": "019619df-4768-76b3-8ab3-4414dcf29ff1" }
]
}

Field reference

FieldRequiredTypeNotes
emailstringUnique; immutable on update
firstNamestring
lastNamestring
uidstringIdentifies the user on update
passwordstring≥ 8 chars; upper + lower + digit + special
fromExternalIdpbooleanControls the activation email (see warning)
allAssetsbooleanGrant all assets instead of listing ASSET permissions
userGroupUidsstring[]Group UIDs
permissions{ uid }[]Permission UIDs
Account-activation emails and fromExternalIdp

The fromExternalIdp flag is what controls whether Stonal sends the new user an account-activation email (the "Set your STONAL password" message). It is the only setting that governs this behaviour for the API — the organization's front-end SSO toggle has no effect on it.

  • fromExternalIdp: false (the default) — the account is created with a local password and the user receives the activation email so they can set or confirm their password. If you omit the field, it defaults to false, so the email is sent.
  • fromExternalIdp: true — the account is treated as federated: no activation email is sent and the user must authenticate through their external identity provider (SSO).

If your users sign in through SSO and you do not want them to receive the "Set your STONAL password" email, you must explicitly pass "fromExternalIdp": true in the create call. Leaving it at false (or omitting it) for SSO users is the most common cause of unexpected activation emails.

Updating vs creating

  • PUT requires the user's uid in the URL path.
  • email cannot be changed on an existing user.
  • There are no partial updates — send the full set of fields you want on the user.
  • The provided permissions and userGroupUids replace the existing ones; any previously assigned groups or permissions not included in the new request will be removed.

Worked end-to-end example

This example walks through three lookups to collect UIDs, then creates a user.

1. Find the group to assign

GET /v1/organizations/DEMO/groups

Abbreviated response:

{
"content": [
{ "uid": "019619df-4768-76b7-81e3-2c56d374df46", "name": "Analysts" }
]
}

Group UID to use: 019619df-4768-76b7-81e3-2c56d374df46

2. Find the profile permission

GET /v1/organizations/DEMO/users/permissions?type=PROFILE

Abbreviated response:

{
"content": [
{ "uid": "019619df-4767-730f-8d31-143712a08141", "name": "Standard profile" }
]
}

Profile permission UID: 019619df-4767-730f-8d31-143712a08141

3. Find the portfolio permission

GET /v1/organizations/DEMO/users/permissions?type=ASSET&subType=PORTFOLIO

Abbreviated response:

{
"content": [
{ "uid": "019619df-4768-76b3-8ab3-4414dcf29ff1", "name": "All portfolios" }
]
}

Portfolio permission UID: 019619df-4768-76b3-8ab3-4414dcf29ff1

4. Create the user

The three UIDs from the lookups feed directly into the request body:

POST /v2/organizations/DEMO/users
{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"fromExternalIdp": false,
"allAssets": false,
"userGroupUids": ["019619df-4768-76b7-81e3-2c56d374df46"],
"permissions": [
{ "uid": "019619df-4767-730f-8d31-143712a08141" },
{ "uid": "019619df-4768-76b3-8ab3-4414dcf29ff1" }
]
}

5. Response

200 OK

The user is created (or updated if the email already existed) and assigned to the Analysts group with the standard profile and all-portfolios permissions.

Responses

  • 200 — user created or updated successfully.

Stonal APIs return a consistent error envelope: { "type", "title", "detail" }. Validation failures (422) replace detail with a per-field errors array.

StatustypeMeaning
400tag:InvalidBody / tag:InvalidContentTypeThe request body or content type is invalid
401tag:UnauthenticatedMissing or expired authentication token
403tag:ForbiddenAccessThe token lacks permission for this resource
422tag:ValidationErrorOne or more fields failed validation (see errors[])
500tag:InternalErrorUnexpected server error
{
"type": "tag:ValidationError",
"title": "Invalid request",
"errors": [
{ "field": "email", "detail": "Email is required" }
]
}