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 notebookGET /execution/notebook/{notebookId}/results- Get execution resultsGET /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
- CRUD Operations: Don't explicitly load - let auto-load handle it
- Execution Operations: Always load the project first
- Long-Running Clients: Unload projects when done to free memory
- Context Managers: Use
withstatements (Python) or try/finally for cleanup - Memory Awareness: The cache auto-cleans at 90% memory pressure, but explicit unloading is better
- Shared Cache: Remember that UI users see the same project state as your API operations