Notebook Management

Full CRUD operations for managing notebooks within investigations. All modification operations automatically load the project into the shared cache.


List Notebooks

GET /api/{tenantId}/{projectId}/notebook/investigation/{investigationId}

Returns all notebooks within an investigation.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
investigationId GUID Yes The investigation identifier

Response (200 OK)

[
  {
    "notebookId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "investigationId": "11111111-2222-3333-4444-555555555555",
    "name": "Main",
    "description": "Primary analysis notebook",
    "dateCreated": "2024-01-15T10:30:00Z",
    "dateModified": "2024-01-20T14:45:00Z",
    "createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "modifiedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "notebookType": 0,
    "notebookOrder": 1.0,
    "lastExecutionDuration": 2.5,
    "blockCount": 12
  },
  {
    "notebookId": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff",
    "investigationId": "11111111-2222-3333-4444-555555555555",
    "name": "Variant Analysis",
    "description": "Process variant exploration",
    "dateCreated": "2024-01-16T09:00:00Z",
    "dateModified": "2024-01-18T11:30:00Z",
    "createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "modifiedBy": null,
    "notebookType": 0,
    "notebookOrder": 2.0,
    "lastExecutionDuration": 1.2,
    "blockCount": 8
  }
]

Error Responses

Not Found (404)

{
  "Error": "Investigation not found",
  "InvestigationId": "11111111-2222-3333-4444-555555555555"
}

Get Notebook

GET /api/{tenantId}/{projectId}/notebook/{notebookId}

Returns detailed information for a specific notebook.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
notebookId GUID Yes The notebook identifier

Response (200 OK)

{
  "notebookId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "investigationId": "11111111-2222-3333-4444-555555555555",
  "name": "Main",
  "description": "Primary analysis notebook",
  "dateCreated": "2024-01-15T10:30:00Z",
  "dateModified": "2024-01-20T14:45:00Z",
  "createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "modifiedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "notebookType": 0,
  "notebookOrder": 1.0,
  "lastExecutionDuration": 2.5,
  "blockCount": 12
}

Create Notebook

POST /api/{tenantId}/{projectId}/notebook/investigation/{investigationId}

Creates a new empty notebook in the investigation.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
investigationId GUID Yes The investigation identifier

Request Body

{
  "name": "Process Analysis",
  "description": "Detailed process analysis workflow",
  "notebookType": 0
}

Request Fields

Field Type Required Description
name string Yes Notebook name (unique within investigation)
description string No Notebook description
notebookType integer No Type (0=Standard, default)

Response (201 Created)

{
  "notebookId": "cccccccc-dddd-eeee-ffff-000000000000",
  "investigationId": "11111111-2222-3333-4444-555555555555",
  "name": "Process Analysis",
  "description": "Detailed process analysis workflow",
  "dateCreated": "2024-03-01T10:00:00Z",
  "dateModified": "2024-03-01T10:00:00Z",
  "createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "modifiedBy": null,
  "notebookType": 0,
  "notebookOrder": 3.0,
  "lastExecutionDuration": 0,
  "blockCount": 0
}

Error Responses

Bad Request (400)

{
  "Error": "Failed to create notebook. The name may already exist in this investigation."
}

Create from Template

POST /api/{tenantId}/{projectId}/notebook/investigation/{investigationId}/from-template

Creates a notebook from a pre-defined template, including all blocks and configurations.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
investigationId GUID Yes The investigation identifier

Request Body

{
  "templateId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "name": "Process Discovery Analysis",
  "description": "Analysis using Process Discovery template"
}

Request Fields

Field Type Required Description
templateId GUID Yes Template to use
name string No Override template name
description string No Override template description

Response (201 Created)

Returns the created notebook with blocks from the template.

Error Responses

Not Found (404)

{
  "Error": "Template not found"
}

Update Notebook

PUT /api/{tenantId}/{projectId}/notebook/{notebookId}

Updates notebook metadata. Supports optimistic locking via DateModified.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
notebookId GUID Yes The notebook identifier

Request Body

{
  "name": "Updated Notebook Name",
  "description": "Updated description",
  "notebookOrder": 2.5,
  "dateModified": "2024-01-20T14:45:00Z"
}

Request Fields

Field Type Required Description
name string Yes Notebook name
description string No Notebook description
notebookOrder decimal No Display order
dateModified datetime No For optimistic locking

Response (200 OK)

Returns the updated notebook.

Optimistic Locking

If dateModified is provided and doesn't match the server's current value, returns 409 Conflict:

{
  "Error": "CONFLICT",
  "Message": "Notebook was modified by another user since you last fetched it",
  "YourDateModified": "2024-01-20T14:45:00Z",
  "CurrentDateModified": "2024-01-21T09:30:00Z",
  "ModifiedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "Resolution": "GET /api/{tenantId}/{projectId}/notebook/{notebookId} to fetch current state, then retry"
}

Delete Notebook

DELETE /api/{tenantId}/{projectId}/notebook/{notebookId}

Permanently deletes a notebook and all its blocks.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
notebookId GUID Yes The notebook identifier

Response (200 OK)

{
  "Message": "Notebook successfully deleted",
  "NotebookId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}

Copy Notebook

POST /api/{tenantId}/{projectId}/notebook/{notebookId}/copy

Creates a complete copy of a notebook including all blocks. Can copy to the same or different investigation.

Path Parameters

Parameter Type Required Description
tenantId GUID Yes The tenant identifier
projectId GUID Yes The project identifier
notebookId GUID Yes Source notebook identifier

Request Body

{
  "name": "Copy of Main Analysis",
  "destinationInvestigationId": "22222222-3333-4444-5555-666666666666"
}

Request Fields

Field Type Required Description
name string No Name for copy (default: "Copy of ")
destinationInvestigationId GUID No Target investigation (default: same investigation)

Response (201 Created)

Returns the newly created notebook copy.


Get Shareable URL

GET /api/{tenantId}/{projectId}/notebook/{notebookId}/url

Generates a shareable URL for direct access to the notebook in the UI.

Response (200 OK)

{
  "notebookId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "url": "https://your-mindzie-instance.com/investigation/12345678/87654321/11111111/notebook/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "relativePath": "/investigation/12345678/87654321/11111111/notebook/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "expiresAt": null
}

Implementation Examples

Python

import requests

BASE_URL = 'https://your-mindzie-instance.com'
TENANT_ID = '12345678-1234-1234-1234-123456789012'
PROJECT_ID = '87654321-4321-4321-4321-210987654321'

class NotebookManager:
    def __init__(self, api_key):
        self.headers = {
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        }

    def list_notebooks(self, investigation_id):
        """List all notebooks in an investigation."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/investigation/{investigation_id}'
        response = requests.get(url, headers=self.headers)
        response.raise_for_status()
        return response.json()

    def get_notebook(self, notebook_id):
        """Get notebook details."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/{notebook_id}'
        response = requests.get(url, headers=self.headers)
        response.raise_for_status()
        return response.json()

    def create_notebook(self, investigation_id, name, description=None):
        """Create a new notebook."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/investigation/{investigation_id}'
        data = {'name': name, 'description': description}
        response = requests.post(url, json=data, headers=self.headers)
        response.raise_for_status()
        return response.json()

    def create_from_template(self, investigation_id, template_id, name=None):
        """Create notebook from template."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/investigation/{investigation_id}/from-template'
        data = {'templateId': template_id, 'name': name}
        response = requests.post(url, json=data, headers=self.headers)
        response.raise_for_status()
        return response.json()

    def update_notebook(self, notebook_id, name, description=None, date_modified=None):
        """Update notebook with optimistic locking."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/{notebook_id}'
        data = {'name': name, 'description': description}
        if date_modified:
            data['dateModified'] = date_modified
        response = requests.put(url, json=data, headers=self.headers)
        if response.status_code == 409:
            conflict = response.json()
            raise Exception(f"Conflict: {conflict['Message']}")
        response.raise_for_status()
        return response.json()

    def delete_notebook(self, notebook_id):
        """Delete a notebook."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/{notebook_id}'
        response = requests.delete(url, headers=self.headers)
        response.raise_for_status()
        return response.json()

    def copy_notebook(self, notebook_id, name=None, destination_investigation=None):
        """Copy a notebook."""
        url = f'{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/{notebook_id}/copy'
        data = {'name': name}
        if destination_investigation:
            data['destinationInvestigationId'] = destination_investigation
        response = requests.post(url, json=data, headers=self.headers)
        response.raise_for_status()
        return response.json()

# Usage
manager = NotebookManager('your-api-key')
investigation_id = '11111111-2222-3333-4444-555555555555'

# List notebooks
notebooks = manager.list_notebooks(investigation_id)
print(f"Found {len(notebooks)} notebooks")

# Create a notebook
notebook = manager.create_notebook(investigation_id, 'New Analysis', 'My workflow')
print(f"Created notebook: {notebook['notebookId']}")

# Copy the notebook
copy = manager.copy_notebook(notebook['notebookId'], 'Copy of New Analysis')
print(f"Created copy: {copy['notebookId']}")

# Update with optimistic locking
try:
    updated = manager.update_notebook(
        notebook['notebookId'],
        'Renamed Analysis',
        date_modified=notebook['dateModified']
    )
except Exception as e:
    print(f"Update failed: {e}")

JavaScript/Node.js

const BASE_URL = 'https://your-mindzie-instance.com';
const TENANT_ID = '12345678-1234-1234-1234-123456789012';
const PROJECT_ID = '87654321-4321-4321-4321-210987654321';

class NotebookManager {
  constructor(apiKey) {
    this.headers = {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    };
  }

  async listNotebooks(investigationId) {
    const url = `${BASE_URL}/api/${TENANT_ID}/${PROJECT_ID}/notebook/investigation/${investigationId}`;
    const response = await fetch(url, { headers: this.headers });
    if (!response.ok) throw new Error(`Failed: ${response.status}`);
    return response.json();
  }

  async createNotebook(investigationId, name, description = null) {
    const url = `${BASE_URL}/api/${TENANT_ID}/${PROJECT_ID}/notebook/investigation/${investigationId}`;
    const response = await fetch(url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify({ name, description })
    });
    if (!response.ok) throw new Error(`Failed: ${response.status}`);
    return response.json();
  }

  async updateNotebook(notebookId, name, description = null, dateModified = null) {
    const url = `${BASE_URL}/api/${TENANT_ID}/${PROJECT_ID}/notebook/${notebookId}`;
    const body = { name, description };
    if (dateModified) body.dateModified = dateModified;

    const response = await fetch(url, {
      method: 'PUT',
      headers: this.headers,
      body: JSON.stringify(body)
    });

    if (response.status === 409) {
      const conflict = await response.json();
      throw new Error(`Conflict: ${conflict.Message}`);
    }
    if (!response.ok) throw new Error(`Failed: ${response.status}`);
    return response.json();
  }

  async deleteNotebook(notebookId) {
    const url = `${BASE_URL}/api/${TENANT_ID}/${PROJECT_ID}/notebook/${notebookId}`;
    const response = await fetch(url, {
      method: 'DELETE',
      headers: this.headers
    });
    if (!response.ok) throw new Error(`Failed: ${response.status}`);
    return response.json();
  }

  async copyNotebook(notebookId, name = null, destinationInvestigationId = null) {
    const url = `${BASE_URL}/api/${TENANT_ID}/${PROJECT_ID}/notebook/${notebookId}/copy`;
    const body = {};
    if (name) body.name = name;
    if (destinationInvestigationId) body.destinationInvestigationId = destinationInvestigationId;

    const response = await fetch(url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(body)
    });
    if (!response.ok) throw new Error(`Failed: ${response.status}`);
    return response.json();
  }
}

// Usage
const manager = new NotebookManager('your-api-key');
const investigationId = '11111111-2222-3333-4444-555555555555';

// List notebooks
const notebooks = await manager.listNotebooks(investigationId);
console.log(`Found ${notebooks.length} notebooks`);

// Create and copy
const notebook = await manager.createNotebook(investigationId, 'New Analysis');
const copy = await manager.copyNotebook(notebook.notebookId, 'Copy of Analysis');

Best Practices

  1. Auto-Load: Don't explicitly load projects for notebook CRUD - it's automatic
  2. Optimistic Locking: Include dateModified in updates to detect conflicts
  3. Templates: Use templates for consistent analysis workflows
  4. Naming: Use descriptive names unique within each investigation
  5. Cleanup: Delete unused notebooks to keep investigations organized