# Sandbox workspace service

Service for managing demo workbooks in sandbox workspaces. All endpoints validate that the provided workspace has the human-readable ID “sandbox”.

OpenAPI file: [`SandboxWorkspaceService.json`](https://dev.nominal.io/api/_downloads/058fec4a6992547093fa8870870ee882/SandboxWorkspaceService.json)

## Endpoints

### Get demo workbooks

GET `/scout/v1/sandbox-workspace/{workspaceRid}/demo-workbooks`

Returns the list of demo workbook RIDs for the given sandbox workspace.

Path parameters

`workspaceRid` string (RID) required
:   ​

Response

`notebookRids` array of string (RID)
:   ​

Request

**curl**

```shell
curl "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks" \
  -H "Authorization: Bearer <api key>"
```

**requests**

```python
import requests

response = requests.get(
    "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
    headers={"Authorization": "Bearer <api key>"},
)
response.raise_for_status()
print(response.json())
```

**httpx**

```python
import httpx

response = httpx.get(
    "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
    headers={"Authorization": "Bearer <api key>"},
)
response.raise_for_status()
print(response.json())
```

**aiohttp**

```python
import asyncio

import aiohttp

async def main() -> None:
    async with aiohttp.ClientSession() as session:
        async with session.get(
            "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
            headers={"Authorization": "Bearer <api key>"},
        ) as response:
            response.raise_for_status()
            print(await response.json())

asyncio.run(main())
```

Response: `200`

```json
{
  "notebookRids": [
    "<notebook rid>"
  ]
}
```

### Set demo workbooks

PUT `/scout/v1/sandbox-workspace/{workspaceRid}/demo-workbooks`

Sets the list of demo workbook RIDs for the given sandbox workspace, replacing any existing entries.

Path parameters

`workspaceRid` string (RID) required
:   ​

Body

`notebookRids` array of string (RID)
:   ​

Response

No content (HTTP 204).

Request

**curl**

```shell
curl -X PUT "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks" \
  -H "Authorization: Bearer <api key>" \
  -H "Content-Type: application/json" \
  --data '{
  "notebookRids": [
    "<notebook rid>"
  ]
}'
```

**requests**

```python
import requests

response = requests.put(
    "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
    headers={"Authorization": "Bearer <api key>"},
    json={"notebookRids": ["<notebook rid>"]},
)
response.raise_for_status()
```

**httpx**

```python
import httpx

response = httpx.put(
    "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
    headers={"Authorization": "Bearer <api key>"},
    json={"notebookRids": ["<notebook rid>"]},
)
response.raise_for_status()
```

**aiohttp**

```python
import asyncio

import aiohttp

async def main() -> None:
    async with aiohttp.ClientSession() as session:
        async with session.put(
            "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
            headers={"Authorization": "Bearer <api key>"},
            json={"notebookRids": ["<notebook rid>"]},
        ) as response:
            response.raise_for_status()

asyncio.run(main())
```

Response: `204`

No content.

### Add demo workbooks

POST `/scout/v1/sandbox-workspace/{workspaceRid}/demo-workbooks`

Appends workbooks to the existing demo workbook list. If archiveOnLabelConflict is true, existing workbooks with an exact label-set match are archived along with all related resources instead of raising an error. The cascade performs a full graph traversal: starting from the workbook’s data scope (asset RIDs or run RIDs), it alternates between discovering runs linked to assets and assets linked to runs until convergence. All discovered assets, runs, datasets (from both asset and run data scopes), and events (from snapshot refs and from all discovered assets) are then archived. This ensures no orphaned resources remain when demo workbooks are replaced. Partial (subset/superset) label conflicts always raise an error.

Path parameters

`workspaceRid` string (RID) required
:   ​

Body

`notebookRids` array of string (RID)
:   ​

`archiveOnLabelConflict` boolean
:   When true, existing workbooks with an exact label-set match are archived instead of raising an error, along with all related resources discovered via full graph traversal: the workbook’s data scope seeds initial assets or runs, then the system alternates between discovering runs linked to assets and assets linked to runs until no new resources are found. All discovered assets, runs, datasets (from both asset and run data scopes), and events (from the workbook’s snapshot refs and from all discovered assets) are archived. This is intended for demo workbook replacement flows where all resources are recreated from scratch. Partial (subset/superset) label conflicts always raise an error. Defaults to false.

Response

No content (HTTP 204).

Request

**curl**

```shell
curl -X POST "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks" \
  -H "Authorization: Bearer <api key>" \
  -H "Content-Type: application/json" \
  --data '{
  "notebookRids": [
    "<notebook rid>"
  ],
  "archiveOnLabelConflict": true
}'
```

**requests**

```python
import requests

response = requests.post(
    "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
    headers={"Authorization": "Bearer <api key>"},
    json={"notebookRids": ["<notebook rid>"], "archiveOnLabelConflict": True},
)
response.raise_for_status()
```

**httpx**

```python
import httpx

response = httpx.post(
    "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
    headers={"Authorization": "Bearer <api key>"},
    json={"notebookRids": ["<notebook rid>"], "archiveOnLabelConflict": True},
)
response.raise_for_status()
```

**aiohttp**

```python
import asyncio

import aiohttp

async def main() -> None:
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "https://api.gov.nominal.io/api/scout/v1/sandbox-workspace/<workspace rid>/demo-workbooks",
            headers={"Authorization": "Bearer <api key>"},
            json={"notebookRids": ["<notebook rid>"], "archiveOnLabelConflict": True},
        ) as response:
            response.raise_for_status()

asyncio.run(main())
```

Response: `204`

No content.

## Errors

[Errors](https://dev.nominal.io/api/legacy.md#errors) shows how these errors are sent.

Each error’s `errorName` is `SandboxWorkspace:` followed by its name below.

| Status | Name | Notes |
| --- | --- | --- |
| 400 | `DemoWorkbookAlreadyArchived` |  |
| 400 | `DemoWorkbookDescriptionRequired` |  |
| 400 | `DemoWorkbookDuplicateLabels` |  |
| 400 | `DemoWorkbookLabelsRequired` |  |
| 400 | `DemoWorkbookMustBePublished` |  |
| 400 | `DemoWorkbookTitleRequired` |  |
| 400 | `WorkspaceNotSandbox` |  |

Page design based on [Scalar](https://scalar.com), MIT License.
