1520 lines
29 KiB
Markdown
1520 lines
29 KiB
Markdown
# Majoor API Reference
|
||
|
||
**Base Path**: `/mjr/am`
|
||
**Version**: 2.5.4
|
||
**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
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Batch Update Ratings
|
||
```http
|
||
POST /mjr/am/assets/rating
|
||
```
|
||
|
||
Rates many assets in one request. Each asset is updated with the same per-asset
|
||
write path (per-asset lock + Comfy-core write-through); there is no single
|
||
transaction across assets, so missing or failing ids are reported per id.
|
||
|
||
Rate limit: 10 requests / 60 s. `ids` is capped at 500 per request
|
||
(`INVALID_INPUT` above that). `rating` is clamped to 0-5. Send
|
||
`X-MJR-RTSYNC: on` to enqueue rating/tags file sync for the updated assets.
|
||
|
||
**Request Body**:
|
||
```json
|
||
{
|
||
"ids": [1, 2, 3],
|
||
"rating": 4
|
||
}
|
||
```
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"data": {
|
||
"updated_ids": [1, 2, 3],
|
||
"failed": [],
|
||
"rating": 4
|
||
}
|
||
}
|
||
```
|
||
|
||
Missing ids are reported in `failed` while the rest are still processed
|
||
(`meta.partial` is `true`):
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"data": {
|
||
"updated_ids": [1],
|
||
"failed": [{ "id": 999, "code": "NOT_FOUND", "error": "Asset not found: 999" }],
|
||
"rating": 2
|
||
},
|
||
"meta": { "partial": true, "attempted": 2, "updated": 1, "failed": 1 }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 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
|
||
{
|
||
"ids": ["asset_123", "asset_124", "asset_125"]
|
||
}
|
||
```
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"data": {
|
||
"deleted": 2,
|
||
"deleted_ids": ["asset_123", "asset_124"],
|
||
"failed_ids": ["asset_125"]
|
||
}
|
||
}
|
||
```
|
||
|
||
**Error Meta**: validation is all-or-nothing (nothing is deleted on rejection). Rejections carry the offending ids in `meta`:
|
||
- `meta.missing_ids` — ids not found (`NOT_FOUND`).
|
||
- `meta.rejected_ids` — ids rejected by path validation (`FORBIDDEN` / `INVALID_INPUT`).
|
||
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"error": "Assets not found: [asset_125]",
|
||
"code": "NOT_FOUND",
|
||
"meta": { "missing_ids": ["asset_125"] }
|
||
}
|
||
```
|
||
|
||
**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.
|
||
|
||
---
|
||
|
||
### Trash Status
|
||
```http
|
||
GET /mjr/am/trash
|
||
```
|
||
|
||
**Purpose**: Report how many freedesktop trash entries came from Majoor's folders (output, input, custom roots). Linux/BSD only; the response never contains file paths. Rate-limited to 20 requests per 60 seconds.
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"data": {
|
||
"supported": true,
|
||
"count": 3,
|
||
"bytes": 1048576
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Empty Trash
|
||
```http
|
||
POST /mjr/am/trash/empty
|
||
```
|
||
|
||
**Purpose**: Permanently remove the freedesktop trash entries whose original location was inside a Majoor folder. Entries from outside Majoor's folders, orphans, and symlink-reachable payloads are never touched; a trash directory is only used when it belongs to the current user and is not group/world-writable. Directories inside the trash are never removed (reported in `skipped_dirs`). Requires the same permission as asset deletion (`allow_delete` / `MAJOOR_ALLOW_DELETE=1`); rate-limited to 3 requests per 60 seconds.
|
||
|
||
**Response**:
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"data": {
|
||
"removed": 3,
|
||
"bytes": 1048576,
|
||
"failed": [],
|
||
"skipped_dirs": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
`failed` lists entries that could not be removed (`name` + `error`); the remaining entries are still removed. `skipped_dirs` counts directory payloads, which are left in place.
|
||
|
||
---
|
||
|
||
## 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+*
|