# Majoor API Reference **Base Path**: `/mjr/am` **Version**: 2.4.9 **Last Updated**: April 7, 2026 --- ## Compatibility ### Versioning - **Current API**: `/mjr/am/*` (unversioned, stable) - **Version Alias**: `/mjr/am/v1/*` → redirects to `/mjr/am/*` (308 Permanent Redirect) - **Legacy Alias**: `/majoor/version` → redirects to `/mjr/am/version` ### Authentication Most endpoints require authentication when ComfyUI auth is enabled: - **Read operations**: May work without auth (depends on configuration) - **Write operations**: Require authentication or API token - **CSRF Protection**: All state-changing endpoints require `X-Requested-With: XMLHttpRequest` or `X-CSRF-Token` ### API Token For remote access or when `MAJOOR_API_TOKEN` is configured: ``` X-MJR-Token: # OR Authorization: Bearer ``` --- ## Table of Contents - [Health & Diagnostics](#health--diagnostics) - [Settings & Configuration](#settings--configuration) - [Search & Discovery](#search--discovery) - [Asset Operations](#asset-operations) - [Indexing & Scanning](#indexing--scanning) - [Collections & Custom Roots](#collections--custom-roots) - [Viewer & Metadata](#viewer--metadata) - [Download & Export](#download--export) - [Database Maintenance](#database-maintenance) - [Utilities](#utilities) --- ## Health & Diagnostics ### Health Summary ```http GET /mjr/am/health ``` **Response**: Extension health summary including version, status, and basic metrics. ```json { "ok": true, "data": { "version": "2.4.9", "status": "healthy", "indexed_count": 1234, "scopes_available": ["output", "input", "custom", "collections"] } } ``` --- ### Health Counters ```http GET /mjr/am/health/counters ``` **Response**: Indexed counters for all scopes. ```json { "ok": true, "data": { "output": { "count": 1000, "last_scan": "2026-02-28T10:00:00Z" }, "input": { "count": 200, "last_scan": "2026-02-28T10:00:00Z" }, "custom": { "count": 34, "last_scan": "2026-02-28T10:00:00Z" }, "collections": { "count": 5 } } } ``` --- ### Database Health ```http GET /mjr/am/health/db ``` **Response**: Database diagnostics including schema version, integrity status, and statistics. ```json { "ok": true, "data": { "schema_version": 7, "integrity_check": "ok", "page_count": 1024, "freelist_count": 128, "cache_size": 2000 } } ``` --- ### Runtime Configuration ```http GET /mjr/am/config ``` **Response**: Current runtime configuration snapshot. ```json { "ok": true, "data": { "output_directory": "/path/to/output", "index_directory": "/path/to/_mjr_index", "media_probe_backend": "auto", "db_timeout": 30.0, "max_connections": 8, "safe_mode": true, "allow_symlinks": false } } ``` --- ### Version Information ```http GET /mjr/am/version ``` **Response**: Extension version and build information. ```json { "ok": true, "data": { "version": "2.4.9", "branch": "main", "build_date": "2026-03-29", "python_version": "3.11.0", "comfyui_version": "0.13.0" } } ``` --- ### Tool Status ```http GET /mjr/am/tools/status ``` **Response**: Availability status of external tools (ExifTool, FFprobe). ```json { "ok": true, "data": { "exiftool": { "available": true, "version": "12.40" }, "ffprobe": { "available": true, "version": "6.0" }, "backend": "both" } } ``` --- ## Settings & Configuration ### Probe Backend Settings ```http GET /mjr/am/settings/probe-backend POST /mjr/am/settings/probe-backend ``` **GET**: Read current probe mode. **POST**: Set probe mode. **Request Body** (POST): ```json { "mode": "auto" // "auto", "exiftool", "ffprobe", "both" } ``` **Response**: ```json { "ok": true, "data": { "mode": "auto", "available": ["exiftool", "ffprobe"] } } ``` --- ### Output Directory Override ```http GET /mjr/am/settings/output-directory POST /mjr/am/settings/output-directory ``` **GET**: Read output directory override. **POST**: Set output directory override. **Request Body** (POST): ```json { "path": "/path/to/custom/output" } ``` **Response**: ```json { "ok": true, "data": { "path": "/path/to/custom/output", "exists": true, "writable": true } } ``` ### Index Directory Override ```http GET /mjr/am/settings/index-directory POST /mjr/am/settings/index-directory ``` **GET**: Read the current index directory path. **Response**: ```json { "ok": true, "data": { "index_directory": "/path/to/_mjr_index" } } ``` **POST**: Set a new index directory path. > Requires write access (loopback or valid `X-MJR-Token`). **Request Body** (POST): ```json { "index_directory": "/path/to/local/mjr_index" } ``` Send an empty string to clear the override and revert to the default (`/_mjr_index/`): ```json { "index_directory": "" } ``` **Response** (POST): ```json { "ok": true, "data": { "index_directory": "/path/to/local/mjr_index", "requires_restart": true } } ``` The new path is persisted to the `.mjr_index_directory_override` sidecar file and the in-process environment variables. The change takes effect after ComfyUI is restarted. **Error codes**: | Code | Meaning | |---|---| | `INVALID_INPUT` | Path exists but is a file, not a directory | | `INVALID_INPUT` | Path was given as a non-empty string but the parent directory does not exist | | `DB_ERROR` | Sidecar file could not be written | --- ### Metadata Fallback Toggles ```http GET /mjr/am/settings/metadata-fallback POST /mjr/am/settings/metadata-fallback ``` **GET**: Read fallback toggles for metadata extraction. **POST**: Set fallback toggles. **Request Body** (POST): ```json { "image": true, // Use internal parser if ExifTool fails "media": true // Use hachoir if ffprobe unavailable } ``` **Response**: ```json { "ok": true, "data": { "image": true, "media": true } } ``` --- ## Search & Discovery ### Search with Filters ```http GET /mjr/am/search ``` **Query Parameters**: | Parameter | Type | Description | |-----------|------|-------------| | `q` | string | Search query (full-text) | | `scope` | string | Scope: `output`, `input`, `custom`, `collections` | | `root_id` | string | Custom root ID (for custom scope) | | `collection_id` | string | Collection ID (for collections scope) | | `kind` | string | File kind: `image`, `video`, `audio`, `model3d`, `all` | | `min_rating` | integer | Minimum rating (0-5) | | `workflow` | boolean | Filter by workflow presence | | `date_from` | string | Start date (ISO 8601) | | `date_to` | string | End date (ISO 8601) | | `size_from` | integer | Minimum file size (bytes) | | `size_to` | integer | Maximum file size (bytes) | | `width_from` | integer | Minimum image width | | `width_to` | integer | Maximum image width | | `height_from` | integer | Minimum image height | | `height_to` | integer | Maximum image height | | `sort` | string | Sort field: `relevance`, `name`, `date`, `size`, `rating` | | `order` | string | Sort order: `asc`, `desc` | | `page` | integer | Page number (1-based) | | `page_size` | integer | Items per page (default: 50) | | `hide_png_siblings` | boolean | Hide PNGs when video exists | **Example**: ```http GET /mjr/am/search?q=fantasy&scope=output&kind=image&min_rating=4&sort=date&order=desc&page=1&page_size=50 ``` **Response**: ```json { "ok": true, "data": { "total": 150, "page": 1, "page_size": 50, "total_pages": 3, "items": [ { "id": "asset_123", "filename": "fantasy_character.png", "path": "/path/to/output/fantasy_character.png", "type": "image", "size": 2048576, "width": 1024, "height": 1536, "rating": 5, "tags": ["fantasy", "character"], "created_at": "2026-02-28T10:00:00Z", "metadata": { "prompt": "A fantasy character...", "model": "SDXL", "steps": 30 } } ] } } ``` --- ### List Assets (Paginated) ```http GET /mjr/am/list ``` **Query Parameters**: Same as search, but without `q` parameter. **Purpose**: List assets without full-text search (faster for browsing). --- ### Asset Details ```http GET /mjr/am/asset/{asset_id} ``` **Response**: Full asset details including metadata. ```json { "ok": true, "data": { "id": "asset_123", "filename": "fantasy_character.png", "path": "/path/to/output/fantasy_character.png", "type": "image", "extension": "png", "size": 2048576, "width": 1024, "height": 1536, "rating": 5, "tags": ["fantasy", "character"], "created_at": "2026-02-28T10:00:00Z", "modified_at": "2026-02-28T10:00:00Z", "metadata": { "prompt": "A fantasy character...", "negative_prompt": "ugly, blurry", "model": "SDXL", "sampler": "DPM++ 2M Karras", "steps": 30, "cfg_scale": 7.0, "seed": 123456789, "workflow": { ... } }, "thumbnails": { "small": "/mjr/am/thumbnail/asset_123?size=small", "medium": "/mjr/am/thumbnail/asset_123?size=medium", "large": "/mjr/am/thumbnail/asset_123?size=large" } } } ``` --- ### Metadata Lookup ```http GET /mjr/am/metadata ``` **Query Parameters**: | Parameter | Type | Description | |-----------|------|-------------| | `type` | string | Scope type: `output`, `input`, `custom` | | `filename` | string | Filename | | `subfolder` | string | Subfolder path (optional) | | `root_id` | string | Custom root ID (for custom scope) | **Example**: ```http GET /mjr/am/metadata?type=output&filename=image.png&subfolder=2026-02-28 ``` **Response**: ```json { "ok": true, "data": { "filename": "image.png", "path": "/path/to/output/2026-02-28/image.png", "metadata": { ... } } } ``` --- ## Asset Operations ### Update Rating ```http POST /mjr/am/asset/rating ``` **Request Body**: ```json { "asset_id": "asset_123", "rating": 5 // 0-5 } ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "rating": 5, "synced_to_file": true } } ``` --- ### Update Tags ```http POST /mjr/am/asset/tags ``` **Request Body**: ```json { "asset_id": "asset_123", "tags": ["fantasy", "character", "sdxl"] } ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "tags": ["fantasy", "character", "sdxl"], "synced_to_file": true } } ``` --- ### Rename Asset ```http POST /mjr/am/asset/rename ``` **Request Body**: ```json { "asset_id": "asset_123", "new_filename": "new_name.png" } ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "old_path": "/path/to/old_name.png", "new_path": "/path/to/new_name.png" } } ``` **Note**: Requires `MAJOOR_ALLOW_RENAME=1` if Safe Mode is enabled. --- ### Delete Single Asset ```http POST /mjr/am/asset/delete ``` **Request Body**: ```json { "asset_id": "asset_123" } ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "deleted": true, "path": "/path/to/deleted_file.png" } } ``` **Note**: Requires `MAJOOR_ALLOW_DELETE=1` if Safe Mode is enabled. --- ### Bulk Delete Assets ```http POST /mjr/am/assets/delete ``` **Request Body**: ```json { "asset_ids": ["asset_123", "asset_124", "asset_125"] } ``` **Response**: ```json { "ok": true, "data": { "deleted_count": 3, "deleted_ids": ["asset_123", "asset_124", "asset_125"], "failed": [] } } ``` **Note**: Requires `MAJOOR_ALLOW_DELETE=1` if Safe Mode is enabled. --- ### Open in Folder ```http POST /mjr/am/open-in-folder ``` **Request Body**: ```json { "asset_id": "asset_123" } ``` or: ```json { "filepath": "/absolute/path/to/file.png" } ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "path": "/path/to/output", "opened": true } } ``` **Note**: Requires `MAJOOR_ALLOW_OPEN_IN_FOLDER=1` if Safe Mode is enabled. This route only asks the OS file manager to reveal an existing local file; it does not read, write, delete, or download file contents. Because of that, `filepath` targets are not restricted to indexed or configured asset roots for this action. --- ### Collect Files ```http POST /mjr/am/collect-files ``` Bundles an asset, its embedded workflow, the traced prompt text, and all media inputs referenced by the workflow into a ZIP written next to the asset. See [COLLECT_FILES.md](COLLECT_FILES.md) for the full guide. **Request Body**: ```json { "filepath": "/absolute/path/to/file.png" } ``` **Response**: ```json { "ok": true, "data": { "zip_path": "/path/to/output/render_collected.zip", "zip_name": "render_collected.zip", "directory": "/path/to/output", "fallback_used": false, "copied": 3, "missing": [], "inputs": [{ "name": "photo.png", "path": "/path/to/input/photo.png", "status": "ok" }], "models": [{ "name": "sdxl.safetensors", "path": "/path/to/checkpoints/sdxl.safetensors", "status": "located" }], "has_workflow": true, "has_prompt": true, "has_prompt_text": true } } ``` **Notes**: - `filepath` must resolve inside the ComfyUI output/input directories or a registered custom root. - When the asset folder is not writable, the ZIP falls back to `output/_mjr_collected/` and `fallback_used` is `true`. - Model files are listed in the manifest with resolved paths but never copied into the ZIP. - Rate limited (10 requests/minute) and requires write access. --- ### Stage to Input ```http POST /mjr/am/stage-to-input ``` **Request Body**: ```json { "asset_id": "asset_123" } ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "staged_path": "/path/to/input/staged_file.png", "staged": true } } ``` **Purpose**: Copy/link asset to ComfyUI input directory for workflow use. --- ## Indexing & Scanning ### Scan Scope/Root ```http POST /mjr/am/scan ``` **Request Body**: ```json { "scope": "output", // "output", "input", "custom" "root_id": null, // Custom root ID (for custom scope) "incremental": true, // Incremental scan (default) "force": false // Force full rescan } ``` **Response**: ```json { "ok": true, "data": { "scan_id": "scan_123", "scope": "output", "status": "started", "estimated_duration_ms": 5000 } } ``` **Note**: Scan runs in background. Status available via health endpoints. --- ### Index Explicit Files ```http POST /mjr/am/index-files ``` **Request Body**: ```json { "files": [ { "filename": "image.png", "subfolder": "2026-02-28", "type": "output" } ], "origin": "generation" // "generation", "manual", "import" } ``` **Response**: ```json { "ok": true, "data": { "indexed_count": 1, "failed": [] } } ``` **Purpose**: Index specific files (used for real-time generation tracking). --- ### Reset Index ```http POST /mjr/am/index/reset ``` **Request Body**: ```json { "scope": "output", // Scope to reset "clear_assets": true, // Clear asset records "clear_metadata": true, // Clear metadata cache "clear_fts": true, // Clear full-text search index "clear_journal": true // Clear scan journal } ``` **Response**: ```json { "ok": true, "data": { "scope": "output", "reset": true, "rescan_triggered": true } } ``` --- ## Collections & Custom Roots ### List Collections ```http GET /mjr/am/collections ``` **Response**: ```json { "ok": true, "data": { "collections": [ { "id": "collection_123", "name": "Best Characters", "item_count": 25, "created_at": "2026-02-28T10:00:00Z", "modified_at": "2026-02-28T12:00:00Z" } ] } } ``` --- ### Create/Update Collection ```http POST /mjr/am/collections/create POST /mjr/am/collections/update ``` **Request Body** (Create): ```json { "name": "Best Characters", "items": [ { "asset_id": "asset_123", "scope": "output" } ] } ``` **Request Body** (Update): ```json { "collection_id": "collection_123", "name": "Updated Name", "items": [...], "add_items": [...], // Items to add "remove_items": [...] // Items to remove } ``` **Response**: ```json { "ok": true, "data": { "collection_id": "collection_123", "name": "Updated Name", "item_count": 30 } } ``` --- ### Delete Collection ```http POST /mjr/am/collections/delete ``` **Request Body**: ```json { "collection_id": "collection_123" } ``` **Response**: ```json { "ok": true, "data": { "collection_id": "collection_123", "deleted": true } } ``` **Note**: Does not delete assets, only the collection definition. --- ### List Custom Roots ```http GET /mjr/am/custom-roots ``` **Response**: ```json { "ok": true, "data": { "roots": [ { "id": "root_123", "path": "/path/to/custom/dir", "name": "Custom Directory", "added_at": "2026-02-28T10:00:00Z" } ] } } ``` --- ### Add Custom Root ```http POST /mjr/am/custom-roots/add ``` **Request Body**: ```json { "path": "/path/to/custom/dir", "name": "Custom Directory" } ``` **Response**: ```json { "ok": true, "data": { "root_id": "root_123", "path": "/path/to/custom/dir", "added": true } } ``` --- ### Remove Custom Root ```http POST /mjr/am/custom-roots/remove ``` **Request Body**: ```json { "root_id": "root_123" } ``` **Response**: ```json { "ok": true, "data": { "root_id": "root_123", "removed": true } } ``` --- ## Viewer & Metadata ### Viewer Info ```http GET /mjr/am/viewer/info ``` **Query Parameters**: | Parameter | Type | Description | |-----------|------|-------------| | `asset_id` | string | Asset ID | **Example**: ```http GET /mjr/am/viewer/info?asset_id=asset_123 ``` **Response**: ```json { "ok": true, "data": { "asset_id": "asset_123", "filename": "fantasy_character.png", "type": "image", "width": 1024, "height": 1536, "metadata": { "prompt": "A fantasy character...", "model": "SDXL", "workflow_minimap": { ... } } } } ``` --- ## Download & Export ### Download Asset ```http GET /mjr/am/download ``` **Query Parameters**: | Parameter | Type | Description | |-----------|------|-------------| | `asset_id` | string | Asset ID | | `filename` | string | Filename (for direct download) | | `type` | string | Scope type | | `subfolder` | string | Subfolder path | **Example**: ```http GET /mjr/am/download?asset_id=asset_123 ``` **Response**: File stream with appropriate Content-Type and Content-Disposition headers. --- ### Create Batch ZIP ```http POST /mjr/am/batch-zip ``` **Request Body**: ```json { "asset_ids": ["asset_123", "asset_124", "asset_125"] } ``` **Response**: ```json { "ok": true, "data": { "token": "zip_token_abc123", "expires_at": "2026-02-28T11:00:00Z", "download_url": "/mjr/am/batch-zip/zip_token_abc123" } } ``` --- ### Download Batch ZIP ```http GET /mjr/am/batch-zip/{token} ``` **Response**: ZIP file stream containing all requested assets. --- ## Database Maintenance ### Optimize Database ```http POST /mjr/am/db/optimize ``` **Purpose**: Run `PRAGMA optimize` and `ANALYZE` for performance. **Response**: ```json { "ok": true, "data": { "optimized": true, "duration_ms": 150 } } ``` --- ### Cleanup Case Duplicates ```http POST /mjr/am/db/cleanup-case-duplicates ``` **Purpose**: Remove historical path-case duplicates (Windows-specific). **Response**: ```json { "ok": true, "data": { "duplicates_removed": 5, "kept_canonical": 100 } } ``` --- ### Force-Delete Database ```http POST /mjr/am/db/force-delete ``` **Purpose**: Emergency recovery for corrupted databases. **Response**: ```json { "ok": true, "data": { "deleted": true, "reinitialized": true, "rescan_triggered": true } } ``` **Note**: This endpoint bypasses normal DB-dependent security checks for emergency recovery. --- ## Utilities ### Date Histogram ```http GET /mjr/am/date-histogram ``` **Query Parameters**: | Parameter | Type | Description | |-----------|------|-------------| | `month` | string | Month in YYYY-MM format | | `scope` | string | Scope type | **Example**: ```http GET /mjr/am/date-histogram?month=2026-02&scope=output ``` **Response**: ```json { "ok": true, "data": { "month": "2026-02", "days": [ { "date": "2026-02-01", "count": 50 }, { "date": "2026-02-02", "count": 75 }, ... ] } } ``` **Purpose**: Calendar histogram for date-based filtering UI. --- ### Duplicate Alerts ```http GET /mjr/am/duplicates/alerts ``` **Response**: ```json { "ok": true, "data": { "duplicate_groups": [ { "filename": "image.png", "paths": [ "/path/to/output/image.png", "/path/to/output/subfolder/image.png" ], "count": 2 } ], "total_groups": 5, "total_duplicates": 10 } } ``` --- ### Workflow Library The Workflow tab uses the standard list endpoint with `scope=workflow` plus dedicated workflow routes for JSON content, organization, metadata, and thumbnails. ```http GET /mjr/am/list?scope=workflow&q=*&limit=100&offset=0&sort=workflow_default ``` **Purpose**: List saved workflow JSON files as grid cards. **Response fields include**: - `assets`: workflow cards - `total`: total matched workflows - `roots`: scanned workflow roots - `scope`: `workflow` ```http GET /mjr/am/workflows/content?filepath=C%3A%5Cpath%5Cworkflow.json ``` **Purpose**: Read workflow JSON and prompt metadata for loading or Graph Map inspection. ```http POST /mjr/am/workflows/save POST /mjr/am/workflows/duplicate POST /mjr/am/workflows/move POST /mjr/am/workflows/delete POST /mjr/am/workflows/mark-loaded POST /mjr/am/workflows/favorite POST /mjr/am/workflows/tags POST /mjr/am/workflows/info ``` **Purpose**: Manage workflow files and workflow library metadata. State-changing workflow routes require the normal Majoor write guards, CSRF/auth checks, audit logging, and workflow write rate limiting. ```http GET /mjr/am/workflows/thumbnail-candidates?filepath=...&limit=12 POST /mjr/am/workflows/thumbnail/set GET /mjr/am/workflows/thumbnail?filepath=... GET /mjr/am/workflows/graph-map-thumbnail?filepath=... GET /mjr/am/workflows/model-families POST /mjr/am/workflows/open-root ``` **Purpose**: Support workflow thumbnails, generated Graph Map previews, workflow model-family filtering, and opening the saved workflow root in the OS file manager. ```http GET /mjr/am/settings/workflow-roots POST /mjr/am/settings/workflow-roots ``` **Purpose**: Read and save the Workflow tab root folders. The POST body accepts `workflow_roots` as a string or list of strings. See [WORKFLOWS.md](WORKFLOWS.md) for the user-facing Workflow tab behavior. --- --- ## Security Notes ### CSRF Protection All state-changing endpoints (POST, PUT, PATCH, DELETE) require: - `X-Requested-With: XMLHttpRequest` header, OR - `X-CSRF-Token` header with valid token ### Authentication When ComfyUI auth is enabled: - Write operations require authenticated user - API token can be used for remote access - Token sent via `X-MJR-Token` or `Authorization: Bearer` ### Safe Mode When Safe Mode is enabled (default): - Delete operations require `MAJOOR_ALLOW_DELETE=1` - Rename operations require `MAJOOR_ALLOW_RENAME=1` - Open in folder requires `MAJOOR_ALLOW_OPEN_IN_FOLDER=1`; it may reveal any existing local path supplied to the route, but it does not grant file read/write access ### Rate Limiting Expensive endpoints are rate-limited: - Search: 10 requests/minute per client - Scan: 5 requests/minute per client - Batch ZIP: 3 requests/minute per client - Metadata: 20 requests/minute per client Client identity based on IP address (or `X-Forwarded-For` from trusted proxies). --- ## Error Responses ### Standard Error Format ```json { "ok": false, "error": { "code": "?_CODE", "message": "Human-readable error message", "details": { ... } // Optional additional context } } ``` ### Common Error Codes | Code | HTTP Status | Description | |------|-------------|-------------| | `AUTH_REQUIRED` | 401 | Authentication required | | `FORBIDDEN` | 403 | Operation not allowed (Safe Mode) | | `NOT_FOUND` | 404 | Resource not found | | `INVALID_REQUEST` | 400 | Invalid request body/parameters | | `RATE_LIMITED` | 429 | Too many requests | | `DB_ERROR` | 500 | Database error | | `FILE_SYSTEM_ERROR` | 500 | File system error | | `TOOL_UNAVAILABLE` | 503 | External tool not available | --- ## WebSocket Events ### Real-Time Updates The extension emits ComfyUI API events for real-time updates: - `mjr-asset-added`: New asset indexed - `mjr-asset-updated`: Asset metadata updated - `mjr-scan-complete`: Scan operation completed - `mjr-enrichment-status`: Metadata enrichment status - `mjr-db-restore-status`: Database restore status **Example** (Frontend): ```javascript api.addEventListener("mjr-asset-added", (event) => { const asset = event.detail; // Update UI with new asset }); ``` --- *API Reference Version: 2.0* *Last Updated: April 5, 2026* *Compatible with Majoor Assets Manager v2.4.4+*