Skip to main content

Resources API

A resource is a file -- image, document, video, or anything else -- stored with metadata, tags, and relationships to other entities.

List Resources

Retrieve a paginated list of resources with optional filtering.

GET /v1/resources

Query Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
NamestringFilter by name (partial match)
DescriptionstringFilter by description (partial match)
ContentTypestringFilter by MIME type (partial match, e.g., image/jpeg)
ContentTypesstring[]Filter by MIME type (exact match, repeatable)
OwnerIdintegerFilter by owner group ID
IncludeSubgroupsbooleanWiden OwnerId to the owner group and all of its descendants
Groupsinteger[]Filter by associated group IDs
Tagsinteger[]Filter by tag IDs
Notesinteger[]Filter by associated note IDs
Idsinteger[]Filter by specific resource IDs
CreatedBeforestringFilter by creation date (ISO 8601)
CreatedAfterstringFilter by creation date (ISO 8601)
UpdatedBeforestringFilter by last-updated date (ISO 8601)
UpdatedAfterstringFilter by last-updated date (ISO 8601)
OriginalNamestringFilter by original filename
OriginalLocationstringFilter by original file path/URL
HashstringFilter by file hash
ShowWithoutOwnerbooleanOnly show resources without an owner
ShowWithSimilarbooleanOnly show resources with similar images
ShowDhashZerobooleanOnly show resources whose perceptual DHash is zero
UntaggedbooleanOnly show resources with no tags
MinWidthintegerMinimum image width in pixels
MaxWidthintegerMaximum image width in pixels
MinHeightintegerMinimum image height in pixels
MaxHeightintegerMaximum image height in pixels
ResourceCategoryIdintegerFilter by resource category ID
SeriesIdintegerFilter by series ID
MetaQuerystring[]Filter by metadata conditions (key:value or key:OP:value)
MRQLstringFilter with an MRQL expression (type resource is implied)
MaxResultsintegerLimit the number of results returned
SortBystring[]Sort order

Example

# List all resources
curl http://localhost:8181/v1/resources

# Filter by content type
curl "http://localhost:8181/v1/resources?ContentType=image/jpeg"

# Filter by owner group
curl "http://localhost:8181/v1/resources?OwnerId=5"

# Filter by tags (multiple)
curl "http://localhost:8181/v1/resources?Tags=1&Tags=2"

# Filter images by dimensions
curl "http://localhost:8181/v1/resources?MinWidth=1920&MinHeight=1080"

Response

[
{
"ID": 1,
"Name": "photo.jpg",
"Description": "A sample photo",
"ContentType": "image/jpeg",
"Hash": "abc123...",
"FileSize": 1024000,
"Width": 1920,
"Height": 1080,
"OriginalName": "IMG_0001.jpg",
"OriginalLocation": "/Users/photos/IMG_0001.jpg",
"OwnerId": 5,
"CreatedAt": "2024-01-15T10:30:00Z",
"UpdatedAt": "2024-01-15T10:30:00Z",
"Tags": [...],
"Owner": {...},
"resourceCategory": {...},
"series": {...}
}
]

Get Single Resource

Retrieve details for a specific resource.

GET /v1/resource?id={id}

Example

curl http://localhost:8181/v1/resource?id=123

Upload Resource (File)

Upload one or more files as new resources.

POST /v1/resource
Content-Type: multipart/form-data

Form Parameters

ParameterTypeDescription
resourcefileFile(s) to upload (can be multiple)
NamestringDisplay name for the resource
DescriptionstringDescription text
OwnerIdintegerOwner group ID
Groupsinteger[]Associated group IDs
Tagsinteger[]Tag IDs to apply
Notesinteger[]Note IDs to associate
MetastringJSON metadata object
CategorystringLegacy category string
ContentCategorystringHigh-level content category label
ResourceCategoryIdintegerResource Category ID
OriginalNamestringOriginal filename
OriginalLocationstringOriginal URL/path
WidthintegerManual width override
HeightintegerManual height override
SeriesSlugstringAssign to Series by slug (creates if needed)
SeriesIdintegerAssign to Series by ID
PathNamestringStorage location key configured via -alt-fs or FILE_ALT_NAME_N. Omit it, or send an empty string, to store into the main filesystem

Example

# Upload a single file
curl -X POST http://localhost:8181/v1/resource \
-H "Accept: application/json" \
-F "resource=@/path/to/file.jpg" \
-F "Name=My Photo" \
-F "OwnerId=5" \
-F "Tags=1" \
-F "Tags=2"

# Upload multiple files
curl -X POST http://localhost:8181/v1/resource \
-H "Accept: application/json" \
-F "resource=@/path/to/file1.jpg" \
-F "resource=@/path/to/file2.jpg" \
-F "OwnerId=5"

Response

A file upload always answers with an array, one element per uploaded file, even when a single file was sent:

[
{"ID": 124, "Name": "file1.jpg", ...},
{"ID": 125, "Name": "file2.jpg", ...}
]

This handler also accepts a URL field. When it is non-empty the request behaves as POST /v1/resource/remote described below, and the response is a single resource object rather than an array.

Upload Resource (URL)

Create a resource by downloading from a remote URL.

POST /v1/resource/remote

Parameters

ParameterTypeDescription
URLstringRequired. URL to download from
FileNamestringOverride the filename
NamestringDisplay name
DescriptionstringDescription text
OwnerIdintegerOwner group ID
Groupsinteger[]Associated group IDs
Tagsinteger[]Tag IDs
Notesinteger[]Note IDs to associate
MetastringJSON metadata
CategorystringLegacy category string
ContentCategorystringHigh-level content category label
ResourceCategoryIdintegerResource Category ID
SeriesSlugstringAssign to Series by slug (creates if needed)
GroupCategoryNamestringAuto-create owner group with this category
GroupNamestringAuto-create owner group with this name
GroupMetastringMetadata for auto-created group
PathNamestringAlternative filesystem key to store into (empty = default filesystem)

OriginalName and OriginalLocation are set from the source URL on this endpoint, so supplying them has no effect, and SeriesId is ignored here (use SeriesSlug).

Example

# Download from URL (synchronous)
curl -X POST http://localhost:8181/v1/resource/remote \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"URL": "https://example.com/image.jpg",
"Name": "Downloaded Image",
"OwnerId": 5,
"Tags": [1, 2]
}'

Pass background=true (query or form field) to queue the download instead of blocking. The request then returns 202 Accepted with {"queued": true, "jobs": [...]} rather than the created resource. POST /v1/jobs/download/submit always queues in the background. The URL field accepts multiple URLs separated by newlines for batch imports; a synchronous multi-URL request returns only the first created resource, so use background=true when you need a per-URL result. Legacy alias: POST /v1/download/submit.

Add Local Resource

Add a file that already exists on the server's filesystem.

LocalPath is resolved inside the selected filesystem, not on the host: the main filesystem is rooted at -file-save-path, and an alternative one at the path its -alt-fs entry names. A file staged at <root>/incoming/photo.jpg is therefore /incoming/photo.jpg here.

POST /v1/resource/local

Parameters

ParameterTypeDescription
LocalPathstringRequired. Path to the file, relative to the root of the selected filesystem
PathNamestringStorage location key configured via -alt-fs or FILE_ALT_NAME_N. Omit it, or send an empty string, to read from the main filesystem
NamestringDisplay name. Defaults to the file's base name
DescriptionstringDescription text
MetastringJSON object. Defaults to {}
OwnerIdintegerOwner group ID
Groupsinteger[]Associated group IDs
Tagsinteger[]Tag IDs
Notesinteger[]Note IDs to associate
CategorystringLegacy category string
ContentCategorystringHigh-level content category label
ResourceCategoryIdintegerResource Category ID
OriginalNamestringOriginal filename. Defaults to Name, or to the file's base name when Name is omitted
OriginalLocationstringOriginal URL/path

Example

curl -X POST http://localhost:8181/v1/resource/local \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"LocalPath": "/incoming/existing-file.pdf",
"Name": "Imported Document",
"OwnerId": 5
}'

To read from an alternative filesystem instead, name it with PathName:

curl -X POST http://localhost:8181/v1/resource/local \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"PathName": "archive",
"LocalPath": "/existing-file.pdf"
}'

Edit Resource

Update an existing resource's metadata.

POST /v1/resource/edit

Parameters

ParameterTypeDescription
IDintegerRequired. Resource ID
NamestringNew name
DescriptionstringNew description
OwnerIdintegerNew owner group ID
Groupsinteger[]Replace associated groups
Tagsinteger[]Replace tags
Notesinteger[]Replace associated notes
MetastringReplace metadata JSON
WidthintegerSet width (for images)
HeightintegerSet height (for images)
ResourceCategoryIdintegerResource Category ID
CategorystringLegacy category string
ContentCategorystringHigh-level content category label
OriginalNamestringOriginal filename
OriginalLocationstringOriginal URL/path
SeriesSlugstringAssign to Series by slug (creates if needed)
SeriesIdintegerAssign to Series by ID

Example

curl -X POST http://localhost:8181/v1/resource/edit \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"ID": 123,
"Name": "Updated Name",
"Description": "New description",
"Tags": [1, 2, 3]
}'

Delete Resource

Delete a resource and its file.

POST /v1/resource/delete?Id={id}

Example

curl -X POST "http://localhost:8181/v1/resource/delete?Id=123" \
-H "Accept: application/json"

View Resource Content

Returns a 302 Found redirect to the file's storage location (e.g., /files/ab/cd/abcdef1234...). The browser or HTTP client follows the redirect to retrieve the file.

GET /v1/resource/view?id={id}

Example

# Follow the redirect to download the file
curl -L http://localhost:8181/v1/resource/view?id=123 -o downloaded-file.jpg

Get Resource Preview

Get a thumbnail preview of a resource (for images and videos).

GET /v1/resource/preview?ID={id}

Query Parameters

ParameterTypeDescription
IDintegerRequired. Resource ID
WidthintegerDesired thumbnail width
HeightintegerDesired thumbnail height

Example

# Get default thumbnail
curl http://localhost:8181/v1/resource/preview?ID=123 -o thumb.jpg

# Get specific size
curl "http://localhost:8181/v1/resource/preview?ID=123&Width=200&Height=200" -o thumb.jpg

Get Resource Meta Keys

Get all unique metadata keys used across resources.

GET /v1/resources/meta/keys

Example

curl http://localhost:8181/v1/resources/meta/keys

Response

Each key is returned as an object with a key field:

[{"key": "author"}, {"key": "source"}, {"key": "date_taken"}, {"key": "location"}]

Bulk Operations

Bulk Add Tags

Add tags to multiple resources at once.

POST /v1/resources/addTags

Parameters

ParameterTypeDescription
IDinteger[]Resource IDs to modify
EditedIdinteger[]Tag IDs to add

Example

curl -X POST http://localhost:8181/v1/resources/addTags \
-H "Content-Type: application/json" \
-d '{
"ID": [1, 2, 3],
"EditedId": [10, 11]
}'

Bulk Remove Tags, Replace Tags, Add Groups

These endpoints follow the same pattern as Bulk Add Tags, using ID for the resource IDs and EditedId for the entity IDs to add or remove:

EndpointDescription
POST /v1/resources/removeTagsRemove tags from multiple resources
POST /v1/resources/replaceTagsReplace all tags on multiple resources with a new set
POST /v1/resources/addGroupsAdd groups to multiple resources

Bulk Add Metadata

Add or merge metadata to multiple resources.

POST /v1/resources/addMeta

Parameters

ParameterTypeDescription
IDinteger[]Resource IDs to modify
MetastringJSON metadata to merge

Mass Edit

Apply several edits — tags, related groups, related notes, owner and metadata — to many resources in one transaction. The target set is either an explicit id list or every resource matching a list page's filter (the raw query string of /resources, re-run server-side and scoped to the caller like any other request).

POST /v1/resources/massEdit

Parameters

ParameterTypeDescription
IDinteger[]Explicit selection (target ids, the default). Duplicates are removed.
Targetstring""/ids (default) or filter
FilterstringThe list page's raw query string, e.g. tags=3&ownerId=9&mrql=.... An empty string means every resource visible to you — the set the unfiltered list page shows.
ExpectedCountintegerRequired when Target=filter. The server re-counts the filtered set and refuses with 409 unless the count matches exactly.
TagsOpstringadd, remove or replace; empty = leave tags unchanged
TagIdsinteger[]tag ids for the tags op
GroupsOp / GroupIdsstring / integer[]add/remove/replace the resources' related groups
NotesOp / NoteIdsstring / integer[]add/remove/replace the resources' related notes
OwnerOpstringset or clear
OwnerIdintegerthe new owner group, for OwnerOp=set
MetaOpstringmerge, replace or removeKeys
MetastringJSON object, for merge and replace
MetaKeysstring[]top-level keys to remove, for removeKeys
DryRunbooleanresolve the target set and echo the parsed ops, committing nothing (the far-endpoint and cycle checks run on the real submit)

An unrecognised verb is refused, never defaulted. Every op runs inside one transaction: if any op fails, nothing changes. A group-limited principal may only reference far endpoints (group/note/resource ids) inside its subtree: add and replace validate the named ids, remove refuses to break a link to an entity the caller cannot see, and replace spares existing links to out-of-subtree entities instead of deleting them — while still clearing dangling join rows whose far entity no longer exists. Validated rows are locked for the transaction on Postgres, so a concurrent delete cannot leave a dangling join row behind. For resources in a series, merge with an explicit null value and removeKeys write the explicit-null override into the resource's own meta, so the series does not re-inherit the removed key. Setting the owner always runs last, because re-parenting changes what a group-limited caller can see.

Response

{
"entity": "resource",
"matched": 4211,
"affected": 4211,
"ops": [
{"op": "tags.add", "rowsAffected": 8410},
{"op": "owner.set", "rowsAffected": 4211}
],
"dryRun": false
}

matched is what targeting resolved to; affected is the number of resources the ops were applied to; ops[].rowsAffected sums join rows for relation ops and entity rows for owner and meta ops.

Example

curl -X POST http://localhost:8181/v1/resources/massEdit \
-H "Content-Type: application/json" \
-d '{
"Target": "filter",
"Filter": "tags=3",
"ExpectedCount": 12,
"TagsOp": "add",
"TagIds": [10],
"OwnerOp": "set",
"OwnerId": 2
}'

Errors

StatusMeaning
400malformed request, unknown verb, a filter matching more than the configured ceiling, or a malformed mrql= expression inside Filter
403a group-limited principal asked to clear the owner
404a named id or far endpoint is missing or outside the caller's subtree
409the filter's re-count did not match ExpectedCount, or a group re-parent would create an ownership cycle

The same endpoint exists as POST /v1/notes/massEdit and POST /v1/groups/massEdit, with the entity-appropriate relation ops: notes take Tags/Groups/Resources, groups take Tags/RelatedGroups (RelatedGroupsOp/RelatedGroupIds)/Notes/Resources. For groups the owner op re-parents, and self-ownership and ownership cycles are refused rather than repaired.

Bulk Delete

Delete multiple resources.

POST /v1/resources/delete

Parameters

ParameterTypeDescription
IDinteger[]Resource IDs to delete

Example

curl -X POST http://localhost:8181/v1/resources/delete \
-H "Content-Type: application/json" \
-d '{"ID": [1, 2, 3]}'

Merge Resources

Merge multiple resources into one, combining their metadata and relationships.

POST /v1/resources/merge

Parameters

ParameterTypeDescription
WinnerintegerResource ID to keep
Losersinteger[]Resource IDs to merge and delete
KeepAsVersionbooleanWhen true, each loser's file is saved as a new older version on the winner before the loser is deleted

Example

curl -X POST http://localhost:8181/v1/resources/merge \
-H "Content-Type: application/json" \
-d '{
"Winner": 1,
"Losers": [2, 3, 4]
}'

Rotate Image

Rotate an image resource.

POST /v1/resources/rotate

Parameters

ParameterTypeDescription
IDintegerResource ID
DegreesintegerRotation angle in degrees (any integer; the image is rotated and its bounds resized to fit). 90, 180, and 270 are the common right-angle cases

Example

curl -X POST http://localhost:8181/v1/resources/rotate \
-H "Content-Type: application/json" \
-d '{"ID": 123, "Degrees": 90}'

Recalculate Dimensions

Recalculate width/height for one or more image/video resources.

POST /v1/resource/recalculateDimensions

Parameters

ParameterTypeDescription
IDinteger[]Resource IDs to recalculate

Example

curl -X POST http://localhost:8181/v1/resource/recalculateDimensions \
-H "Content-Type: application/json" \
-d '{"ID": [123, 456]}'

Set Dimensions

Manually set dimensions for a resource.

POST /v1/resources/setDimensions

Parameters

ParameterTypeDescription
IDintegerResource ID
WidthintegerWidth in pixels
HeightintegerHeight in pixels

Crop Image

Crop an image resource to a rectangle, saving the result either as a new version of that resource or as a separate resource.

POST /v1/resources/crop

Parameters

ParameterTypeDescription
IDintegerResource ID
XintegerLeft edge of the crop rectangle in pixels
YintegerTop edge of the crop rectangle in pixels
WidthintegerCrop width in pixels
HeightintegerCrop height in pixels
CommentstringOptional comment. Stored on the new version, or in the new resource's description when AsNewResource is set
AsNewResourcebooleanSave the crop as a separate resource and leave the source untouched. Defaults to false, which versions the source in place

Response

Versioning the source in place returns {"ok": true}. With AsNewResource=true the response also carries the id of the resource the crop was saved as:

{"ok": true, "id": 4211}

The new resource inherits the source's owner, groups, tags, and resource category. Content that already exists is deduplicated: an identical crop responds 409 Conflict naming the resource that already holds those bytes. The check runs against what the caller can see, so for a group-limited user a match outside their subtree is neither reported nor reused.

Trim Video

Trim a video resource to a time range and save the result as a new version. Requires ffmpeg.

POST /v1/resources/trim

Parameters

ParameterTypeDescription
IDintegerResource ID
StartstringStart timestamp (e.g., 00:00:05 or seconds)
EndstringEnd timestamp
CommentstringOptional comment stored on the new version

Set Custom Thumbnail

Replace a resource's thumbnail with a user-uploaded image.

POST /v1/resource/preview?ID={id}
Content-Type: multipart/form-data
ParameterTypeDescription
IDintegerRequired. Resource ID (query param)
thumbnailfileRequired. Image file to use as the thumbnail

On success the response is 204 No Content.

Errors

  • 400: Missing or invalid ID, a body that is not multipart, no thumbnail field, or an image that cannot be decoded
  • 404: Resource not found, or outside the caller's scope

Clear Custom Thumbnail

Remove stored thumbnails so the next preview request regenerates them from the source file.

DELETE /v1/resource/preview?ID={id}

Or using POST: POST /v1/resource/preview/clear?ID={id}.

On success the response is 204 No Content.

Suggested Tags

Get up to eight suggested tags, excluding tags already applied. Suggestions blend:

  • 50% visual similarity: up to 50 accessible neighbors within the configured hash thresholds. Each tagged neighbor has weight 2^(-distance/3); legacy exact-dHash matches without a comparable distance have weight 0.25. A candidate's score is its supporting weight divided by total tagged-neighbor weight plus 2.
  • 30% co-occurrence: resources sharing any currently applied tag, counted once each. Use the exact owner group when at least five other resources match; otherwise use all resources the caller can access. Ownerless resources use this wider population directly. The score is candidate usage divided by matching resource count plus 5. Resources with no tags skip this signal.
  • 20% group popularity: candidate usage divided by tagged resource count plus 5 in the exact owner group, excluding subgroups. Ownerless resources skip this signal.

The target resource is excluded from all evidence counts. Candidate selection takes up to 20 eligible tags from each of the group and co-occurrence sources, excluding applied tags before those limits, plus tags from similar resources. Each candidate receives contributions from all available sources. Ties sort by name, then ID. Missing or failed sources contribute zero without redistributing their weights; a failed local co-occurrence lookup does not trigger wider fallback.

The response is { "suggestions": [{ "ID": 12, "Name": "beach", "score": 0.25, "sources": ["similar", "cooccurrence", "group"] }] }. Source entries are included only when they contribute, in the order shown. Scores are advisory ranking values, not probabilities. The lightbox refreshes recommendations after tag edits settle.

GET /v1/resource/suggestedTags?id={id}

Inline Editing

Edit resource name, description, or a single metadata field with minimal payload.

Edit Name

POST /v1/resource/editName?id={id}

Edit Description

POST /v1/resource/editDescription?id={id}

These endpoints accept the new value in the request body.

Edit Meta

Edit a single metadata field at a dot-notation path using deep merge.

POST /v1/resource/editMeta?id={id}

Query Parameters

ParameterDescription
idRequired. Resource ID

Form Fields

FieldDescription
pathDot-notation path into the Meta field (e.g., author, location.city)
valueJSON-encoded value to set at that path

Response

{"ok": true, "id": 123, "meta": {"author": "Alice", "location": {"city": "London"}}}

Behavior

  • Creates intermediate objects as needed
  • Preserves sibling fields at every nesting level
  • If the path does not exist, it is created
  • If an intermediate key holds a scalar, it is overwritten to become an object
  • Returns the full updated meta in the response

Errors

  • 400: Missing ID, missing path, missing value, invalid JSON, malformed path (empty segments), or corrupt existing meta
  • 404: Resource not found

Resource Versions API

Each resource keeps historical copies of its file. When a new file is uploaded, the previous file is saved as a version.

List Versions

Get all versions for a resource.

GET /v1/resource/versions?resourceId={id}

Query Parameters

ParameterTypeDescription
resourceIdintegerRequired. The resource ID

Example

curl "http://localhost:8181/v1/resource/versions?resourceId=123"

Get Single Version

GET /v1/resource/version?id={versionId}

Query Parameters

ParameterTypeDescription
idintegerRequired. The version ID

Upload New Version

Upload a new file as a version of a resource.

POST /v1/resource/versions?resourceId={id}
Content-Type: multipart/form-data

Parameters

ParameterTypeDescription
resourceIdintegerRequired. The resource ID (query param)
filefileRequired. The file to upload
commentstringOptional comment describing the change

Example

curl -X POST "http://localhost:8181/v1/resource/versions?resourceId=123" \
-H "Accept: application/json" \
-F "file=@/path/to/new-version.jpg" \
-F "comment=Updated resolution"

Restore Version

Restore a previous version as the current resource file.

POST /v1/resource/version/restore

Parameters

ParameterTypeDescription
resourceIdintegerRequired. The resource ID
versionIdintegerRequired. The version ID to restore
commentstringOptional comment for the restore

Example

curl -X POST http://localhost:8181/v1/resource/version/restore \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"resourceId": 123, "versionId": 5, "comment": "Reverting to original"}'

Delete Version

Delete a specific version.

DELETE /v1/resource/version?resourceId={resourceId}&versionId={versionId}

Or using POST:

POST /v1/resource/version/delete?resourceId={resourceId}&versionId={versionId}

Query Parameters

ParameterTypeDescription
resourceIdintegerRequired. The resource ID
versionIdintegerRequired. The version ID

Response

{"status": "deleted"}

Errors

  • 409: The version is the resource's current version, or its last remaining version
  • 400: The version belongs to a different resource
  • 404: The version does not exist

Get Version File

Download the file content of a specific version.

GET /v1/resource/version/file?versionId={versionId}

Query Parameters

ParameterTypeDescription
versionIdintegerRequired. The version ID
dispositionstringOptional. inline asks the browser to render the file in place instead of downloading it, and relaxes X-Frame-Options to SAMEORIGIN so it can be framed. Honoured for application/pdf only; every other content type is served as an attachment whatever this says, because a version file is an arbitrary upload and rendering one inline and same-origin would execute whatever it contains.

Example

curl "http://localhost:8181/v1/resource/version/file?versionId=5" -o version-file.jpg

Cleanup Versions

Remove old versions for a specific resource based on age or count criteria.

POST /v1/resource/versions/cleanup

Parameters

ParameterTypeDescription
resourceIdintegerRequired. The resource ID
keepLastintegerNumber of most recent versions to keep
olderThanDaysintegerDelete versions older than N days
dryRunbooleanIf true, return what would be deleted without deleting

Example

# Preview what would be cleaned up
curl -X POST http://localhost:8181/v1/resource/versions/cleanup \
-H "Content-Type: application/json" \
-d '{"resourceId": 123, "keepLast": 5, "dryRun": true}'

Response

{
"deletedVersionIds": [1, 2, 3],
"count": 3
}

Bulk Cleanup Versions

Remove old versions across multiple resources.

POST /v1/resources/versions/cleanup

Parameters

ParameterTypeDescription
keepLastintegerNumber of most recent versions to keep per resource
olderThanDaysintegerDelete versions older than N days
ownerIdintegerOnly clean up versions for resources owned by this group
dryRunbooleanIf true, return what would be deleted without deleting

Response

{
"deletedByResource": {"123": [1, 2], "456": [3]},
"totalDeleted": 3
}

Compare Versions

Compare two versions of a resource.

GET /v1/resource/versions/compare?resourceId={id}&v1={versionId1}&v2={versionId2}

Same-Resource Comparison

ParameterTypeDescription
resourceIdintegerRequired. The resource ID
v1integerRequired. First version ID
v2integerRequired. Second version ID
curl "http://localhost:8181/v1/resource/versions/compare?resourceId=123&v1=1&v2=5"

Cross-Resource Comparison

Cross-resource comparison is available through the UI at /resource/compare, not through the /v1/ API:

/resource/compare?r1=123&v1=1&r2=456&v2=1

On /resource/compare, v1 and v2 are per-resource version numbers rather than the version IDs the /v1/ endpoint takes, and r2 defaults to r1 when omitted.

Response

{
"version1": { ... },
"version2": { ... },
"sizeDelta": -1024,
"sameHash": false,
"sameType": true,
"dimensionsDiff": true,
"crossResource": false
}
FieldTypeDescription
version1objectThe full version record for the first side
version2objectThe full version record for the second side
resource1objectThe first resource, present only when crossResource is true
resource2objectThe second resource, present only when crossResource is true
sizeDeltaintegerSize difference in bytes (version2 - version1)
sameHashbooleanWhether file hashes match
sameTypebooleanWhether content types match
dimensionsDiffbooleanWhether dimensions differ (width or height)
crossResourcebooleanWhether versions belong to different resources