Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 60 additions & 38 deletions data/api/users.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
const scenarioIdParam = {
name: "scenario_id",
type: "number",
description: "the scenario ID",
};
name: "scenario_id",
type: "number",
description: "the scenario ID",
};

export default {
index: {
endpoint: "/api/v3/scenarios/{scenario_id}/users",
method: "GET",
path_parameters: [scenarioIdParam],
token: { scopes: ["scenarios:delete"] },
},
export default {
index: {
endpoint: "/api/v3/scenarios/{scenario_id}/users",
method: "GET",
path_parameters: [scenarioIdParam],
token: { scopes: ["scenarios:delete"] },
},

update: {
endpoint: "/api/v3/scenarios/{scenario_id}/version",
method: "PUT",
path_parameters: [scenarioIdParam],
parameters: [
{
name: "description",
type: "string",
description: "a short description of the tagged version",
},
],
token: { scopes: ["scenarios:delete"] },
},
create: {
endpoint: "/api/v3/scenarios/{scenario_id}/version",
method: "POST",
path_parameters: [scenarioIdParam],
parameters: [
{
name: "description",
type: "string",
description: "a short description of the tagged version",
},
],
token: { scopes: ["scenarios:delete"] },
},
};
create: {
endpoint: "/api/v3/scenarios/{scenario_id}/users",
method: "POST",
path_parameters: [scenarioIdParam],
parameters: [
{
name: "scenario_users",
type: "array",
description: "array of user objects to add",
},
],
token: { scopes: ["scenarios:delete"] },
},

update: {
endpoint: "/api/v3/scenarios/{scenario_id}/users",
method: "PUT",
path_parameters: [scenarioIdParam],
parameters: [
{
name: "scenario_users",
type: "array",
description: "array of user objects to update",
},
],
token: { scopes: ["scenarios:delete"] },
},

destroy: {
endpoint: "/api/v3/scenarios/{scenario_id}/users",
method: "DELETE",
path_parameters: [scenarioIdParam],
parameters: [
{
name: "scenario_users",
type: "array",
description: "array of user objects to remove",
},
],
token: { scopes: ["scenarios:delete"] },
},

destroyAll: {
endpoint: "/api/v3/scenarios/{scenario_id}/users/destroy_all",
method: "DELETE",
path_parameters: [scenarioIdParam],
token: { scopes: ["scenarios:delete"] },
},
};
2 changes: 1 addition & 1 deletion data/releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const mainReleases = [
export const apiChangelog = [
{
date: "Feb 5, 2026",
title: "Scenario interpolation",
title: "Scenario interpolation and Saved scenario users endpoint",
file: "2026-01.md",
tag: "2026.01",
},
Expand Down
224 changes: 224 additions & 0 deletions docs/api/saved-scenarios.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that for the following added operations:

  • Listing users on a saved scenario (saved-secnarios.md:277)
  • Adding users to a saved scenario (saved-secnarios.md:309)
  • Updating user roles (saved-secnarios.md:362)
  • Removing users from a saved scenario (saved-secnarios.md:395)

We are kind of skipping the convention of using
I understand this would bulk this page quite a lot but this drives me to another point, perhaps it would have been better to put the entire "Managing saved scenario users" section into its own page similar to how we do it for the standard scenario users? Not asking for a change, just offering my thoughts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the thought and in general agree that some standard practices would be nice.

I decided to try and keep the docs minimal in this case, because I would hope that in the future we would remove the ability to manage the underlying scenario users completely and just manage everything via saved scenario users. Because the functionalities are for a passthrough - managing saved scenario users via an engine endpoint, I decided to just keep everything together.

TLDR:
In the long run I would propose making a 'Users' page which describes managing users (which only happens through one endpoint and at the level of saved scenarios). For now I'd rather not bog down users with extra info.

Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,230 @@ Authorization: Bearer YOUR_TOKEN
}
```

## Managing saved scenario users

Saved scenarios support collaborative access through user management. You can invite other users to view or edit your saved scenarios by granting them specific roles.

:::info Historical scenario propagation
User changes propagate asynchronously to all historical versions of the saved scenario.
:::

### Batch requests

All user management operations can be performed in batches: adding, updating, or removing multiple users in one request. The response will contain a JSON array of successfully processed users. When one or more users fail, the response will be `422` and contain the following:

* `success` - The users that were successfully processed.
* `errors` - The users that were not successful, with error details.

This partial success behavior ensures that valid operations are persisted even when some fail, allowing you to see exactly which users succeeded and which failed.

### User roles

There are three roles available for saved scenario users:

* **scenario_owner** - Full control over the scenario, including the ability to manage users, edit settings, and delete the saved scenario
* **scenario_collaborator** - Can view and edit the scenario, but cannot manage users or delete the saved scenario
* **scenario_viewer** - Read-only access to view the scenario

:::info Owner requirements
Every saved scenario must have at least one owner. The API will prevent you from removing the last owner.
:::

See also the [saved scenario access](/main/user_manual/managing-scenarios/scenario-manage-access) page for more information.

### Listing users on a saved scenario

Retrieve all users associated with a saved scenario:

```http title="Example request"
GET /api/v3/saved_scenarios/123/users HTTP/2
Host: engine.energytransitionmodel.com
Accept: application/json
Authorization: Bearer YOUR_TOKEN
```

```json title="Example response"
[
{
"id": 1,
"user_id": 456,
"user_email": null,
"role": "scenario_owner",
"role_id": 0,
"saved_scenario_id": 123
},
{
"id": 2,
"user_id": null,
"user_email": "collaborator@example.com",
"role": "scenario_collaborator",
"role_id": 1,
"saved_scenario_id": 123
}
]
```

### Adding users to a saved scenario

Add one or more users to a saved scenario. Users can be identified by email address, and will receive an invitation to access the scenario.

:::tip Bulk operations
All user management endpoints support bulk operations. You can add, update, or remove multiple users in a single request by providing an array of user objects.
:::

```http title="Example request"
POST /api/v3/saved_scenarios/123/users HTTP/2
Host: engine.energytransitionmodel.com
Accept: application/json
Authorization: Bearer YOUR_TOKEN

{
"saved_scenario_users": [
{
"user_email": "viewer@example.com",
"role": "scenario_viewer"
},
{
"user_email": "collaborator@example.com",
"role": "scenario_collaborator"
}
]
}
```

```json title="Example response"
[
{
"id": 3,
"user_id": null,
"user_email": "viewer@example.com",
"role": "scenario_viewer",
"role_id": 3,
"saved_scenario_id": 123
},
{
"id": 4,
"user_id": null,
"user_email": "collaborator@example.com",
"role": "scenario_collaborator",
"role_id": 1,
"saved_scenario_id": 123
}
]
```

:::info Automatic coupling
If a user with the provided email address already has an ETM account, they will be automatically coupled when added (the `user_id` will be populated). Otherwise, they will receive an invitation email.
:::

### Updating user roles

Change the role of one or more users. Users can be identified by `user_id`, `user_email`, or the SavedScenarioUser `id`.

```http title="Example request"
PUT /api/v3/saved_scenarios/123/users HTTP/2
Host: engine.energytransitionmodel.com
Accept: application/json
Authorization: Bearer YOUR_TOKEN

{
"saved_scenario_users": [
{
"user_email": "viewer@example.com",
"role": "scenario_collaborator"
}
]
}
```

```json title="Example response"
[
{
"id": 3,
"user_id": null,
"user_email": "viewer@example.com",
"role": "scenario_collaborator",
"role_id": 1,
"saved_scenario_id": 123
}
]
```

### Removing users from a saved scenario

Remove one or more users from a saved scenario. Users can be identified by `user_id`, `user_email`, or the SavedScenarioUser `id`.

```http title="Example request"
DELETE /api/v3/saved_scenarios/123/users HTTP/2
Host: engine.energytransitionmodel.com
Accept: application/json
Authorization: Bearer YOUR_TOKEN

{
"saved_scenario_users": [
{
"user_email": "viewer@example.com"
}
]
}
```

The API will return a `204 No Content` response on success.

:::warning Last owner protection
You cannot remove the last owner from a saved scenario. The API will return a `422 Unprocessable Entity` error if you attempt to do so.
:::

### Error handling

User management operations support partial success:

* Each user in a bulk request is processed independently
* Successfully processed users are saved to the database
* Failed users are reported in the error response
* The API returns a `422 Unprocessable Entity` status when any user fails
* The response includes both successful users and errors

```json title="Partial success response example"
{
"success": [
{
"id": 3,
"user_id": null,
"user_email": "viewer@example.com",
"role": "scenario_viewer",
"role_id": 3,
"saved_scenario_id": 123
},
{
"id": 4,
"user_id": 789,
"user_email": null,
"role": "scenario_collaborator",
"role_id": 1,
"saved_scenario_id": 123
}
],
"errors": {
"invalid@example.com": ["role_id"],
"duplicate@example.com": ["duplicate"]
}
}
```

:::info Partial success persistence
When a bulk operation returns partial success, the successful changes **are persisted** to the database. Only the failed operations need to be retried.
:::

Common errors include:

* `duplicate` - User is already associated with the saved scenario
* `role_id` - Invalid role specified
* `not_found` - User not found when updating or removing
* `ownership` - Cannot remove or change the last owner

For managing users on regular scenarios (not saved scenarios), see [Scenario Users](/api/users).

## Update the underlying scenario

As described in [**scenarios vs. saved scenarios**](#scenarios-vs-saved-scenarios), a *saved* scenario is just a way to keep track of a scenario. It allows you to show it in your list of saved scenarios in the ETM web application. You cannot directly change the underlying scenario through the saved scenario.
Expand Down
Loading