Skip to content

REST API

The Fast Video Cataloger Server provides a comprehensive REST API for integration with external applications and custom clients. The API uses JSON for request and response bodies.

API Documentation

Interactive API documentation is available via Swagger UI at:

http://your-server:8754/api/docs

The OpenAPI 3.0 specification is available at:

http://your-server:8754/swagger/v1/swagger.json

Connecting an AI assistant

You do not have to write an HTTP client for that. Fast Video Cataloger ships an MCP server that exposes the catalog as ready-made tools - see Connect an AI Assistant.

Automating the application itself

This API is headless - it reaches the catalog, not the program. To drive the selection, the GUI or the video player, use the C# scripting API from the Script Console instead.

Base URL

All API endpoints are prefixed with /api/v1/.

Authentication

Most endpoints require authentication. Include the session token in requests using either:

Authorization Header (recommended):

Authorization: Bearer {token}

Query String:

/api/v1/videos?token={token}

To obtain a token, use the login endpoint (see Authentication section below).

Roles

The API supports three user roles:

Role Permissions
Viewer Read-only access to all resources
Editor Read and write access (create, update, delete)
Admin Full access including user management

API Keys

Session tokens expire after eight hours, which is fine for a browser but not for a script, a scheduled job or an AI assistant. For those, create an API key: a long-lived credential with its own role that is presented exactly like a session token.

An administrator creates keys through the API keys endpoints (see below), or without any password in the desktop application that shares the catalog: Start > Server > Manage Users (shown once the server is running) > "Create key...". That button is the easiest way to create the first key on a fresh setup, since the desktop application is already connected to its own server as the administrator. The key is shown once, in the response that creates it. Only a hash is stored, so a lost key cannot be recovered - create a new one and revoke the old.

Active keys (not revoked, not expired) are limited to the number of user accounts the server's license allows; creating one beyond that returns 403 with the numbers. Revoke a key to free its slot.

Give each key the least privilege that does the job. A key that only needs to search the catalog should be a Viewer; a key cannot be given more privilege than the user who owns it, and it stops working if that user is deleted or deactivated.

Change the default admin password

Every catalog gets a built-in admin account with the password admin the first time it is opened, so authentication is enforced from the start — but until that password is changed, anyone who can reach the port can log in as the administrator with it. Change it before making the server reachable beyond your own machine. See Authentication and Users.


Authentication Endpoints

Check if Authentication Required

GET /api/v1/auth/required

Returns whether authentication is required for this server.

Response:

{
  "authenticationRequired": true
}

Login

POST /api/v1/auth/login

Authenticate and obtain a session token.

Request Body:

{
  "username": "user@example.com",
  "password": "yourpassword"
}

Response:

{
  "success": true,
  "token": "session-token-string",
  "expiresAt": "2024-01-15T12:00:00Z",
  "role": "Editor"
}

Logout

POST /api/v1/auth/logout

Invalidate the current session token.

Get Session Info

GET /api/v1/auth/session

Get information about the current session.

Response:

{
  "isAuthenticated": true,
  "userId": 1,
  "username": "user@example.com",
  "role": "Editor"
}

API Keys

Long-lived credentials for machine clients. All three endpoints require the Admin role.

Create API Key (Admin)

POST /api/v1/apikeys

Request Body:

{
  "name": "editing workstation agent",
  "role": "Viewer",
  "expiresInDays": 365
}

role defaults to Viewer. Omit expiresInDays for a key that never expires.

Response:

{
  "success": true,
  "data": {
    "id": 3,
    "name": "editing workstation agent",
    "role": "Viewer",
    "createdAt": "2026-08-08T09:12:00Z",
    "expiresAt": "2027-08-08T09:12:00Z",
    "isActive": true,
    "key": "fvc_XCJ1s..."
  }
}

The key field appears in this response and nowhere else. Store it before moving on.

Use it exactly like a session token:

Authorization: Bearer fvc_XCJ1s...

List API Keys (Admin)

GET /api/v1/apikeys

Returns the metadata of every key - name, role, when it was created, and when it was last used. The keys themselves are not stored and are never returned.

Revoke API Key (Admin)

DELETE /api/v1/apikeys/{id}

Deletes the key. It stops working on the next request made with it.


Videos

Search Videos

GET /api/v1/videos

Search and list videos with optional filters.

Query Parameters:

Parameter Type Description
search string Text search query
keywords string Comma-separated keyword filter
matchAllKeywords bool When true a video must carry every keyword in keywords, not just one of them (default false)
actors string Comma-separated actor ids; only videos whose cast includes them are returned - the reverse of GET /videos/{id}/actors
title string Filter by title
genre int Filter by genre ID
minRating int Minimum rating (0-5)
maxRating int Maximum rating (0-5)
offset int Pagination offset (default: 0)
limit int Max results

Response:

{
  "success": true,
  "data": [...],
  "totalCount": 150
}

Get Video

GET /api/v1/videos/{id}

Get details for a specific video.

Get Video Preview Image

GET /api/v1/videos/{id}/image

Returns the video preview image as JPEG binary data.

Stream Video

GET /api/v1/videos/{id}/stream

Stream video content. Supports HTTP Range headers for seeking.

Get Video Thumbnails

Accepts limit and offset. Without them every thumbnail is returned, which for a long video is a large response. totalCount always reports the full number.

GET /api/v1/videos/{id}/thumbnails

Get all scene thumbnails for a video.

Query Parameters:

Parameter Type Description
includeImageData bool Include base64 image data (default: false)

Get Video Actors

GET /api/v1/videos/{id}/actors

Get all actors appearing in a video.

Get Video Tags

GET /api/v1/videos/{id}/tags

Get all tags applied to a video.

Get Random Videos

GET /api/v1/videos/random

Get random videos from the catalog.

Query Parameters:

Parameter Type Description
count int Number of videos to return (default: 10)

Get Videos Pending Indexing

GET /api/v1/videos/pending-indexing

Get videos that need indexing (no length or no thumbnails).

Query Parameters:

Parameter Type Description
limit int Max results
offset int Pagination offset

A newly added folder can leave thousands of entries pending, and each one is a full record — page through them rather than asking for the lot. totalCount in the response is the real total, so you can tell how much is left.

Batch Get Videos

POST /api/v1/videos/batch

Get multiple videos by ID in a single request. Efficient for avoiding N+1 query patterns.

Request Body:

[1, 2, 3, 4, 5]

Add Video (Editor)

POST /api/v1/videos

Add a new video to the catalog. This creates the catalog entry only - it has no thumbnails and no duration until the video is indexed.

Request Body:

{
  "path": "/path/to/video.mp4",
  "title": "Video Title",
  "description": "Description",
  "genre": 1,
  "rating": 4,
  "lengthSeconds": 3600,
  "link": "https://example.com"
}

Index Video (Editor)

POST /api/v1/videos/{id}/index

Index a video that is already in the catalog. Extracts thumbnails and duration, and applies whatever else the server is configured for - file metadata, transcription, face recognition and scene classification.

Indexing a video takes minutes, so the call returns 202 Accepted as soon as the work is queued and continues in the background. Poll GET /api/v1/videos/{id}/thumbnails to follow progress, or GET /api/v1/videos/pending-indexing to see what is still outstanding.

Only one video is indexed at a time. Further requests queue and run in turn, so it is safe to loop over pending-indexing and post each id.

Response:

{ "success": true, "message": "Indexing started for video 12" }

Returns 404 if the video is not in the catalog, or if its file cannot be found on the server.

Update Video (Editor)

PUT /api/v1/videos/{id}

Update video properties.

Request Body:

{
  "title": "New Title",
  "description": "New Description",
  "rating": 5,
  "genre": 2,
  "link": "https://example.com"
}

Delete Video (Editor)

DELETE /api/v1/videos/{id}

Delete a video from the catalog.

Update Video Path (Editor)

PUT /api/v1/videos/{id}/path

Change the file path of a video.

Request Body:

{
  "newPath": "/new/path/to/video.mp4"
}

Set Video Image (Editor)

PUT /api/v1/videos/{id}/image

Set the video preview image. Request body is raw JPEG bytes.

Set Video Genre (Editor)

PUT /api/v1/videos/{id}/genre?genre={genreName}

Set the video genre by name.

Set Video Rating (Editor)

PUT /api/v1/videos/{id}/rating

Request Body:

{
  "rating": 4
}

Tag Video (Editor)

PUT /api/v1/videos/{id}/tags

Request Body:

{
  "tags": "action, drama, thriller"
}

Remove Tag from Video (Editor)

DELETE /api/v1/videos/{id}/tags/{tagInstanceId}

Add Actor to Video (Editor)

POST /api/v1/videos/{id}/cast/{actorId}

Remove Actor from Video (Editor)

DELETE /api/v1/videos/{id}/cast/{actorId}

Get Video Companion Images

GET /api/v1/videos/{id}/images

Get companion images associated with a video.

Remove Video Companion Image (Editor)

DELETE /api/v1/videos/{id}/images/{imageId}

Get Video Cover Images

GET /api/v1/videos/{id}/covers

Get front and back cover images for a video.

Set Video Cover Images (Editor)

PUT /api/v1/videos/{id}/covers

Request Body:

{
  "frontCoverId": 123,
  "backCoverId": 456
}

Get Video Extended Properties

GET /api/v1/videos/{id}/properties

Get custom extended properties for a video.

Set Video Extended Property (Editor)

PUT /api/v1/videos/{id}/properties

Request Body:

{
  "propertyName": "CustomField",
  "value": "Custom Value"
}

This sets the value on the video only. For the property to show up in the client's search filter and property lists it must also be registered once with POST /api/v1/properties/meta (see Properties (Metadata) below).

Get Video Archives

GET /api/v1/videos/{id}/archives

Get archive IDs containing this video.

Archive Video (Editor)

POST /api/v1/videos/{id}/archive

Request Body:

{
  "archiveId": 1
}

Actors

Search Actors

GET /api/v1/actors

Query Parameters:

Parameter Type Description
search string Text search query
firstName string Filter by first name
lastName string Filter by last name
tags string Comma-separated tag filter
skipImageData bool Skip portrait data (default: true)
limit int Max results

Get All Actors

GET /api/v1/actors/all

Get Actor

GET /api/v1/actors/{id}

Get Actor Portrait

GET /api/v1/actors/{id}/portrait

Returns portrait image as JPEG binary data.

Get Actor Tags

GET /api/v1/actors/{id}/tags

Get Actor Companion Images

GET /api/v1/actors/{id}/images

Create Actor (Editor)

POST /api/v1/actors

Request Body:

{
  "firstName": "John",
  "lastName": "Doe",
  "bio": "Actor biography",
  "link": "https://example.com"
}

Update Actor (Editor)

PUT /api/v1/actors/{id}

Delete Actor (Editor)

DELETE /api/v1/actors/{id}

Tag Actor (Editor)

POST /api/v1/actors/{id}/tags

Request Body:

{
  "tags": "lead, supporting"
}

Remove Actor Tag (Editor)

DELETE /api/v1/actors/{id}/tags/{tagInstanceId}

Remove Actor Companion Image (Editor)

DELETE /api/v1/actors/{id}/images/{imageId}

Get Actor Face Embedding

GET /api/v1/actors/{id}/embedding

Get face recognition embedding data for an actor.

Save Actor Face Embedding (Editor)

PUT /api/v1/actors/{id}/embedding

Request Body:

{
  "embeddingData": "base64-encoded-embedding"
}

Delete Actor Face Embedding (Editor)

DELETE /api/v1/actors/{id}/embedding

Tags

Get Video Tags

GET /api/v1/tags/video

Get all video tag definitions.

Get Thumbnail Tags

GET /api/v1/tags/thumbnail

Get all scene/thumbnail tag definitions.

Get Actor Tags

GET /api/v1/tags/actor

Get all actor tag definitions.

Get Tag Groups

GET /api/v1/tags/groups

Get all tag groups.

Get Video Tags in Group

GET /api/v1/tags/groups/{groupId}/video

Get Thumbnail Tags in Group

GET /api/v1/tags/groups/{groupId}/thumbnail

Get Genres

GET /api/v1/tags/genres

Get all genre definitions.

Create Video Tag (Editor)

POST /api/v1/tags

Request Body:

{
  "name": "Action",
  "parentId": null,
  "color": 16711680,
  "description": "Action scenes",
  "link": ""
}

Update Video Tag (Editor)

PUT /api/v1/tags/{id}

Delete Video Tag (Editor)

DELETE /api/v1/tags/{id}

Delete Thumbnail Tag (Editor)

DELETE /api/v1/tags/thumbnail/{id}

Create Tag Group (Editor)

POST /api/v1/tags/groups

Request Body:

{
  "name": "My Group"
}

Rename Tag Group (Editor)

PUT /api/v1/tags/groups/{id}

Request Body:

{
  "name": "New Name"
}

Delete Tag Group (Editor)

DELETE /api/v1/tags/groups/{id}

Add Tag to Group (Editor)

POST /api/v1/tags/groups/{groupId}/tags/{tagId}

Remove Tag from Group (Editor)

DELETE /api/v1/tags/groups/{groupId}/tags/{tagId}

Get Video Tag Usage

GET /api/v1/tags/{id}/usage

Get count of videos using this tag.

Get Thumbnail Tag Usage

GET /api/v1/tags/thumbnail/{id}/usage

Get Actor Tag Usage

GET /api/v1/tags/actor/{id}/usage

Rename Genre (Editor)

PUT /api/v1/tags/genres

Request Body:

{
  "oldName": "Old Genre",
  "newName": "New Genre"
}

Bins (Collections)

Get All Bins

GET /api/v1/bins

Get Child Bins

GET /api/v1/bins/{id}/bins

Get bins nested within a bin.

Get Videos in Bin

GET /api/v1/bins/{id}/videos

Query Parameters:

Parameter Type Description
limit int Max results
offset int Pagination offset

A bin can hold the whole catalog, so page large bins. totalCount is the full size of the bin regardless of how many records were returned.

Get Bin Query

GET /api/v1/bins/{id}/query

Get the smart bin query (for dynamic bins).

Create Bin (Editor)

POST /api/v1/bins

Request Body:

{
  "label": "My Collection",
  "parentId": null,
  "color": 255
}

Update Bin (Editor)

PUT /api/v1/bins/{id}

Request Body:

{
  "label": "New Label",
  "color": 16711680
}

Delete Bin (Editor)

DELETE /api/v1/bins/{id}

Set Bin Query (Editor)

PUT /api/v1/bins/{id}/query

Set a VideoQuery to make this a smart/dynamic bin.

Add Video to Bin (Editor)

POST /api/v1/bins/{id}/videos

Request Body:

{
  "videoId": 123
}

Remove Video from Bin (Editor)

DELETE /api/v1/bins/{id}/videos/{videoId}

Thumbnails (Scenes)

Search Thumbnails

GET /api/v1/thumbnails/search

Query Parameters:

Parameter Type Description
keywords string Comma-separated keyword filter
search string Text search query
videoId long Filter by video ID
limit int Max results

Get Thumbnail

GET /api/v1/thumbnails/{id}

Get thumbnail entry metadata.

Get Thumbnail Image

GET /api/v1/thumbnails/{id}/image

Returns scene image as JPEG binary data.

Get Thumbnail Tags

GET /api/v1/thumbnails/{id}/tags

Get Thumbnail Extended Properties

GET /api/v1/thumbnails/{id}/properties

Add Thumbnail (Editor)

POST /api/v1/thumbnails

Request Body:

{
  "videoId": 123,
  "timeMs": 5000,
  "imageData": "base64-encoded-jpeg"
}

Delete Thumbnail (Editor)

DELETE /api/v1/thumbnails/{id}

Tag Thumbnail (Editor)

POST /api/v1/thumbnails/{id}/tags

Request Body:

{
  "tags": "outdoor, sunny"
}

Remove Thumbnail Tag (Editor)

DELETE /api/v1/thumbnails/{id}/tags/{tagInstanceId}

Set Thumbnail Extended Property (Editor)

PUT /api/v1/thumbnails/{id}/properties

Request Body:

{
  "propertyName": "CustomField",
  "value": "Custom Value"
}

Get Thumbnail Classifications

GET /api/v1/thumbnails/{id}/classifications

Get AI scene classifications for a thumbnail.

Save Thumbnail Classification (Editor)

POST /api/v1/thumbnails/{id}/classifications

Request Body:

{
  "label": "outdoor",
  "confidence": 0.95
}

Delete Thumbnail Classifications (Editor)

DELETE /api/v1/thumbnails/{id}/classifications

Clips

Create Clip (Editor)

POST /api/v1/clips

Request Body:

{
  "videoId": 123,
  "startMs": 10000,
  "endMs": 20000
}

Get Clip

GET /api/v1/clips/{id}

Delete Clip (Editor)

DELETE /api/v1/clips/{id}

Playlists

Get All Playlists

GET /api/v1/playlists

Create Playlist (Editor)

POST /api/v1/playlists

Request Body:

{
  "name": "My Playlist"
}

Delete Playlist (Editor)

DELETE /api/v1/playlists/{id}

Get Playlist Clip IDs

GET /api/v1/playlists/{id}/clips

Get Playlist Clip at Index

GET /api/v1/playlists/{id}/clips/{index}

Add Clip to Playlist (Editor)

POST /api/v1/playlists/{id}/clips

Request Body:

{
  "clipId": 123,
  "index": 0
}

Remove Clip from Playlist (Editor)

DELETE /api/v1/playlists/{id}/clips/{index}

Subtitles

Get Video Subtitles

GET /api/v1/videos/{videoId}/subtitles

Get the transcript of a video as timed lines.

Accepts limit and offset. Without them the whole transcript is returned - a feature-length one is a few hundred kilobytes of JSON - while totalCount always reports the full number of lines.

Create Subtitle (Editor)

POST /api/v1/videos/{videoId}/subtitles

Request Body:

{
  "startMs": 1000,
  "endMs": 5000,
  "text": "Hello, world!"
}

Delete All Video Subtitles (Editor)

DELETE /api/v1/videos/{videoId}/subtitles

Get Subtitle

GET /api/v1/subtitles/{id}

Delete Subtitle (Editor)

DELETE /api/v1/subtitles/{id}

Search Subtitles

GET /api/v1/subtitles/search

Query Parameters:

Parameter Type Description
search string Text to search for
offset int Pagination offset
limit int Max results

Returns the IDs of the matching subtitle lines — not video IDs. Resolve each one with GET /api/v1/subtitles/{id} to get its text, its videoFileID and its start and end time:

GET /api/v1/subtitles/search?search=launch
{"success":true,"data":[133,208,342,391,827]}

GET /api/v1/subtitles/133
{"success":true,"data":{"id":133,"videoFileID":2,"startTime":271.62,"endTime":276.34,
 "text":"...along with the Gateway and the Space Launch System"}}

Passing these IDs to an endpoint that expects a video ID will silently return the wrong video or none at all.


Archives

Get All Archives

GET /api/v1/archives

Get Archive

GET /api/v1/archives/{id}

Create Archive (Editor)

POST /api/v1/archives

Request Body:

{
  "name": "Backup 2024",
  "description": "Annual backup",
  "location": "External Drive A"
}

Update Archive (Editor)

PUT /api/v1/archives/{id}

Delete Archive (Editor)

DELETE /api/v1/archives/{id}

Scene Classifications

Get Distinct Labels

GET /api/v1/classifications/labels

Get all unique scene classification labels in the catalog.

Search by Label

GET /api/v1/classifications/search

Query Parameters:

Parameter Type Description
label string Classification label to search
minConfidence double Minimum confidence score (0-1)
offset int Pagination offset
limit int Max results

Get Video Classifications

GET /api/v1/classifications/video/{videoId}

Get all scene classifications for thumbnails in a video.


Properties (Metadata)

Extended properties are described by a registry. Each registry row has a property class, an aspect that holds the property name, and a value. The classes are:

property (class) aspect value
video_property the property name edit for a text field, or combo={a,b,c} for a dropdown
thumbnail_property the property name same as above, for scene (thumbnail) properties
category the property name a grouping label shown in the client, e.g. video, audio, xmp
namespace the property name the XMP namespace URI, for properties imported from XMP

A property must have a video_property (or thumbnail_property) row to appear in the client's search filter, column picker and property editor. category and namespace rows are optional annotations, keyed by the property name.

Get Property Metadata

GET /api/v1/properties/meta/{property}

Get all registry rows for one class. {property} is the class, e.g. GET /api/v1/properties/meta/video_property lists every defined video property name.

Set Property Metadata (Editor)

POST /api/v1/properties/meta

Register a property. To define a custom video property called MyCustomField:

Request Body:

{
  "property": "video_property",
  "aspect": "MyCustomField",
  "value": "edit"
}

Use "value": "combo={red,green,blue}" for a dropdown. Set values on individual videos with PUT /api/v1/videos/{id}/properties.

Delete Property Metadata (Editor)

DELETE /api/v1/properties/meta

Remove a property definition. For the video_property and thumbnail_property classes this also deletes the stored values of that property from every video or scene, and, when the last definition of the name goes, its category and namespace rows. To delete the MyCustomField property defined above:

Request Body:

{
  "property": "video_property",
  "aspect": "MyCustomField"
}

Statistics

Get Combined Stats

GET /api/v1/stats

Get combined catalog statistics.

Response:

{
  "success": true,
  "data": {
    "videoCount": 1500,
    "actorCount": 250,
    "imageCount": 5000,
    "thumbnailCount": 0
  }
}

Get Video Count

GET /api/v1/stats/videos

Get Actor Count

GET /api/v1/stats/actors

Get Image Count

GET /api/v1/stats/images

Get Thumbnail Count

GET /api/v1/stats/thumbnails

Uploads

Upload Video (Editor)

POST /api/v1/upload/video

Upload a video file using multipart/form-data.

Request: - Content-Type: multipart/form-data - Field: file - The video file

Response:

{
  "success": true,
  "uploadId": 123
}

Complete Video Upload (Editor)

POST /api/v1/upload/video/{uploadId}/complete

Finalize upload and add video to catalog.

Request Body:

{
  "title": "Video Title",
  "description": "Description",
  "genre": 1,
  "rating": 4,
  "link": ""
}

Cancel Upload (Editor)

DELETE /api/v1/upload/video/{uploadId}

Cancel upload and delete temporary files.

Upload Image (Editor)

POST /api/v1/upload/image

Upload an image file, optionally as a companion image for a video.

Form Fields: - file - The image file - videoId (optional) - Associate as companion image


Response Format

All API responses use a consistent wrapper format:

Success Response

{
  "success": true,
  "data": { ... },
  "totalCount": 100
}

Error Response

{
  "success": false,
  "error": "Error message description"
}

Simple Result Response

For operations that don't return data:

{
  "success": true,
  "message": "Operation completed successfully"
}

HTTP Status Codes

Code Description
200 Success
400 Bad Request - Invalid parameters
401 Unauthorized - Authentication required
403 Forbidden - Insufficient permissions (requires Editor/Admin)
404 Not Found - Resource not found
500 Server Error - Internal error
503 Service Unavailable - Catalog not available

CORS

The API supports Cross-Origin Resource Sharing (CORS) to allow web clients from any origin.


Rate Limiting

Currently, no rate limiting is enforced. Clients should implement reasonable request throttling to avoid overloading the server.


Version History

Version Date Changes
2.0 2024 Full API implementation with 80+ endpoints covering all catalog functionality
1.0 2023 Initial REST API with basic read operations