Project Cache

The Project Cache API manages in-memory project loading for API operations. Understanding when projects need to be loaded is essential for efficient API usage.

Key Concepts

Unified Cache Architecture

The API and UI share the same in-memory cache. When you load a project via the API, it's the same cache the UI uses. This means:

  • Shared State: API operations see the same data as UI users
  • Shared Results: Execution results are visible to both API and UI
  • No Divergence: Impossible for API and UI to have different views of a project

Operation Categories

API operations fall into three categories with different caching requirements:

Category Description Project Load Required? Examples
Direct DB Read-only operations No GET endpoints, tenant/user management
Auto-Load Modification operations No (auto-loads) POST/PUT/DELETE on investigations, notebooks, blocks
Requires Load Execution operations Yes Execute notebook, get execution results

Auto-Load Pattern (Simplified Workflow)

For most CRUD operations, you don't need to explicitly load the project. The API automatically loads the project when needed:

# OLD workflow (no longer needed for CRUD):
# manager.load_project(project_id)  # Not required!

# NEW workflow - just call the operation directly:
response = requests.put(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/notebook/{notebook_id}",
    json={"Name": "Updated Name"},
    headers=headers
)
# Project loads automatically if needed

When Explicit Load IS Required

Explicit project loading is still required for execution operations:

  • POST /execution/notebook/{notebookId} - Execute notebook
  • GET /execution/notebook/{notebookId}/results - Get execution results
  • GET /execution/status/{notebookId} - Check execution status

Load Project into Cache

GET /api/{tenantId}/project/{projectId}/load

Loads a project into the shared cache. Use this before executing notebooks.

Path Parameters

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

Response (200 OK)

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "projectName": "Purchase Order Analysis",
  "tenantName": "acme-corp",
  "investigationCount": 5,
  "notebookCount": 12,
  "datasetCount": 3,
  "loadedFromCache": false,
  "message": "Project loaded from database"
}

Response Fields

Field Type Description
projectId GUID Project identifier
projectName string Name of the project
tenantName string Name of the tenant
investigationCount integer Number of investigations
notebookCount integer Number of notebooks
datasetCount integer Number of datasets
loadedFromCache boolean True if already in cache, false if loaded from database
message string Human-readable status message

Cache Behavior

Scenario Response Performance
First call (cache miss) loadedFromCache: false ~1000ms (database query)
Subsequent calls (cache hit) loadedFromCache: true ~75ms (13x faster)
After 30 min inactivity Cache expires Next call reloads

Cache Properties

  • Duration: 30 minutes after last access
  • Auto-refresh: Any API call to the project resets the 30-minute timer
  • Shared: Same cache used by UI and API
  • Memory Management: Automatic cleanup at 90% memory pressure

Unload Project from Cache

DELETE /api/{tenantId}/project/{projectId}/unload

Removes a project from the cache, freeing memory.

Path Parameters

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

Response (200 OK)

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "wasInCache": true,
  "message": "Project unloaded from cache successfully"
}

Workflow Examples

Workflow A: CRUD Operations (Auto-Load)

For creating, updating, or deleting investigations, notebooks, or blocks:

import requests

headers = {"Authorization": f"Bearer {API_KEY}"}

# Just call the operation directly - no load needed!
response = requests.post(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/investigation",
    json={"name": "New Investigation", "description": "Created via API"},
    headers=headers
)
# Project auto-loads if needed

Workflow B: Notebook Execution (Requires Load)

For executing notebooks and retrieving results:

import requests
import time

headers = {"Authorization": f"Bearer {API_KEY}"}

# Step 1: Load project (REQUIRED for execution)
response = requests.get(
    f"{BASE_URL}/api/{TENANT_ID}/project/{PROJECT_ID}/load",
    headers=headers
)
print(f"Project loaded: {response.json()['projectName']}")

# Step 2: Execute notebook
response = requests.post(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/execution/notebook/{NOTEBOOK_ID}",
    headers=headers
)
print(f"Execution queued: {response.json()['status']}")

# Step 3: Poll for completion
while True:
    response = requests.get(
        f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/execution/status/{NOTEBOOK_ID}",
        headers=headers
    )
    status = response.json()
    print(f"Status: {status['status']} ({status['progress']}%)")

    if status['status'] == 'Completed':
        break
    time.sleep(2)

# Step 4: Get results
response = requests.get(
    f"{BASE_URL}/api/{TENANT_ID}/{PROJECT_ID}/execution/notebook/{NOTEBOOK_ID}/results",
    headers=headers
)
results = response.json()

# Step 5: Unload project (optional cleanup)
requests.delete(
    f"{BASE_URL}/api/{TENANT_ID}/project/{PROJECT_ID}/unload",
    headers=headers
)

Implementation Examples

cURL

# Load project into cache
curl -X GET "https://your-mindzie-instance.com/api/12345678-1234-1234-1234-123456789012/project/87654321-4321-4321-4321-210987654321/load" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Unload project from cache
curl -X DELETE "https://your-mindzie-instance.com/api/12345678-1234-1234-1234-123456789012/project/87654321-4321-4321-4321-210987654321/unload" \
  -H "Authorization: Bearer YOUR_API_KEY"

Python

import requests

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

class ProjectCacheManager:
    def __init__(self, token):
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        self.loaded_projects = set()

    def load_project(self, project_id):
        """Load project into cache (required for execution operations)."""
        url = f'{BASE_URL}/api/{TENANT_ID}/project/{project_id}/load'
        response = requests.get(url, headers=self.headers)
        response.raise_for_status()

        result = response.json()
        self.loaded_projects.add(project_id)

        status = "from cache" if result['loadedFromCache'] else "from database"
        print(f"Project '{result['projectName']}' loaded {status}")

        return result

    def unload_project(self, project_id):
        """Unload project from cache."""
        url = f'{BASE_URL}/api/{TENANT_ID}/project/{project_id}/unload'
        response = requests.delete(url, headers=self.headers)
        response.raise_for_status()

        self.loaded_projects.discard(project_id)
        return response.json()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for project_id in list(self.loaded_projects):
            self.unload_project(project_id)

# Usage with context manager
with ProjectCacheManager('your-api-key') as cache:
    result = cache.load_project('87654321-4321-4321-4321-210987654321')
    # Execute notebooks here...
# Projects automatically unloaded when exiting

JavaScript/Node.js

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

class ProjectCacheManager {
  constructor(token) {
    this.headers = {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    };
    this.loadedProjects = new Set();
  }

  async loadProject(projectId) {
    const url = `${BASE_URL}/api/${TENANT_ID}/project/${projectId}/load`;
    const response = await fetch(url, { headers: this.headers });

    if (!response.ok) throw new Error(`Failed: ${response.status}`);

    const result = await response.json();
    this.loadedProjects.add(projectId);

    console.log(`Loaded: ${result.projectName} (from cache: ${result.loadedFromCache})`);
    return result;
  }

  async unloadProject(projectId) {
    const url = `${BASE_URL}/api/${TENANT_ID}/project/${projectId}/unload`;
    const response = await fetch(url, {
      method: 'DELETE',
      headers: this.headers
    });

    this.loadedProjects.delete(projectId);
    return response.json();
  }

  async unloadAll() {
    await Promise.all(
      Array.from(this.loadedProjects).map(id => this.unloadProject(id))
    );
  }
}

// Usage
const cache = new ProjectCacheManager('your-api-key');
try {
  await cache.loadProject('87654321-4321-4321-4321-210987654321');
  // Execute notebooks here...
} finally {
  await cache.unloadAll();
}

Best Practices

  1. CRUD Operations: Don't explicitly load - let auto-load handle it
  2. Execution Operations: Always load the project first
  3. Long-Running Clients: Unload projects when done to free memory
  4. Context Managers: Use with statements (Python) or try/finally for cleanup
  5. Memory Awareness: The cache auto-cleans at 90% memory pressure, but explicit unloading is better
  6. Shared Cache: Remember that UI users see the same project state as your API operations