Files
MajoorWaldi-ComfyUI-Majoor-…/docs/API_REFERENCE.md
T
Wiley Walarus a1f855407e Merge remote-tracking branch 'upstream/main' into feat/bulk-tags
# Conflicts:
#	dist/chunks/FloatingViewer--x61GAzy.js
#	dist/chunks/FloatingViewer-BiEeZ0CS.js
#	dist/chunks/FloatingViewer-D8bHMZQk.js
#	dist/chunks/LiveStreamTracker-B56NDtCP.js
#	dist/chunks/LiveStreamTracker-CIt2lOUV.js
#	dist/chunks/LiveStreamTracker-C_DbXPzd.js
#	dist/chunks/SidebarWorkflowSection-DrCNoS04.js
#	dist/chunks/TagsEditor-2AmCKykg.js
#	dist/chunks/Viewer-W-G40j3K.js
#	dist/chunks/ViewerPortal-gWBXDS-v.js
#	dist/chunks/config-C9a-wT5M.js
#	dist/chunks/events-C65wdOHm.js
#	dist/chunks/events-tsQFsZrH.js
#	dist/chunks/floatingViewerManager-BzaQii21.js
#	dist/chunks/genInfo-DD23-sz2.js
#	dist/chunks/model3dRenderer-BnFOXFbn.js
#	dist/chunks/openMajoorSettings-2yBqYLc0.js
#	dist/chunks/openMajoorSettings-IdB9HDLb.js
#	dist/chunks/sideBySide-Dahw1GfU.js
#	dist/chunks/tooltipShortcuts-Ury2eTek.js
#	dist/chunks/viewerOpenRequest-WCeGPIlE.js
#	dist/chunks/viewerRuntimeHosts-n0pKLECw.js
#	dist/entry.js
2026-11-04 12:37:11 -08:00

1412 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
- **OpenAPI compat (read-only)**: `/mjr/am/compat/assets/*` mirrors the shape of ComfyUI core `/api/assets/*` (`GET /mjr/am/compat/assets`, `/{id}`, `/{id}/content`, `/{id}/tags`, `/tags/refine`, `/seed/status`, `HEAD /hash/{hash}`). The former `/api/v2/assets/*` paths are deprecated and only served when `MJR_AM_LEGACY_API_V2_ASSETS=1`.
### 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: <your-token>
# OR
Authorization: Bearer <your-token>
```
---
## 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 (`<output>/_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
}
}
```
---
### Bulk Update Tags (add/remove across assets)
```http
POST /mjr/am/assets/tags
```
Merges tags per asset on the server: `(existing ∪ add) − remove` (case-insensitive; existing tags keep their casing, added tags keep the request's casing). Each asset is updated independently under its own lock — assets that fail (missing id, or the merged set would exceed 50 tags) are reported in `failed` while the rest still succeed (`meta.partial` is `true` in that case).
**Request Body**:
```json
{
"ids": [1, 2, 3, 4],
"add": ["fantasy"],
"remove": ["draft"]
}
```
- `ids`: 1-500 asset IDs per request
- `add` / `remove`: tag lists (same sanitising as the single-asset route); at least one non-empty tag after sanitising
**Response**:
```json
{
"ok": true,
"data": {
"updated_ids": [1, 2],
"unchanged_ids": [3],
"failed": [{"id": 4, "code": "NOT_FOUND", "error": "Asset not found: 4"}],
"tags": {"1": ["fantasy"], "2": ["fantasy", "character"], "3": ["fantasy"]}
},
"meta": {"updated": 2, "unchanged": 1, "failed": 1, "partial": true}
}
```
**Notes**:
- Rate limited to 10 requests / 60 s (like bulk delete).
- Honours `X-MJR-RTSYNC` like the single-asset route (file metadata sync enqueued for updated assets only).
- One audit log entry per request (`assets_tags`, target truncated to the first 25 ids).
---
### 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+*