Skip to content

Curation

Curation is everything people add on top of pushed files: names, categories, tags, collections, pack details and the project list used by pull jobs. Pushes never overwrite it. Reads need read scope; every change needs write. Admin-only operations are marked.

Method and path Purpose
PATCH /entries/{entry_id} Rename, set category, change current version, deprecate
POST /entries/category/bulk Set or clear the category on many entries
POST /entries/variant-group/bulk Group or ungroup sibling variants
DELETE /entries/{entry_id} Hard-delete (Admin)

Send any of:

Field Type Description
name string 1 to 200 characters.
category string or null 1 to 120 characters. null clears it.
category_locked boolean Locks the category against automatic updates from pushes.
object_id integer Make this version current. Must be in the entry’s lineage (409 LINEAGE_CONFLICT otherwise). While someone else has the prop checked out, changing it fails with 409 LOCKED naming the holder, unless you are an admin. Prefer POST /entries/{entry_id}/restore, which also records the change in activity; see Versions.
status active or deprecated Admin only (403 for members). Deprecated entries drop out of search unless include_deprecated=true but stay readable and pullable.

Returns the full entry detail.

Terminal window
curl -X PATCH -H "Authorization: Bearer $PH_KEY" -H "Content-Type: application/json" \
-d '{"name": "Rusty barrel", "category": "Barrels", "category_locked": true}' \
https://api.prophouse.dev/api/v1/entries/412
{ "entry_ids": [412, 413, 414], "category": "Barrels" }

Up to 1,000 ids. category is required: a string assigns and locks it, null clears the override so automatic categorization applies again. All-or-nothing; unknown ids fail the call with 404 ENTRY_NOT_FOUND and details.missing. Returns {"category", "affected"}.

{ "entry_ids": [500, 501, 502], "action": "group", "label": "SM_Crate" }
Field Description
entry_ids Up to 1,000 ids.
action group puts them in one variant group (reusing their existing group if exactly one exists, renamed to label if given). ungroup removes them from their groups.
label Optional group label.

Returns {"variant_group_id", "affected"} (variant_group_id is null after ungroup). Entries spread across two or more existing groups: 409 CONFLICT with details.existing_group_ids; ungroup first.

Hard-deletes an entry and its versions that nothing else needs. Returns 204. An entry that was ever downloaded or pulled cannot be deleted (409 CONFLICT); deprecate it instead.

Method and path Body Response
GET /tags?limit=&cursor= {"tags": [{id, name, color, entry_count}], "next_cursor"} (opaque cursor)
POST /tags {"name", "color"?} 201 {id, name, color}
PATCH /tags/{tag_id} {"name"?, "color"?} {id, name, color}
DELETE /tags/{tag_id} 204; removes the tag from every entry
PUT /entries/{entry_id}/tags/{tag_id} 204; idempotent
DELETE /entries/{entry_id}/tags/{tag_id} 204; idempotent
POST /entries/tags/bulk {"tag_id", "entry_ids", "action": "add" or "remove"} {"tag_id", "action", "affected"}

Tag names are 1 to 80 characters and unique regardless of case; a duplicate is 409 CONFLICT with details.existing_id. color is #rrggbb or null. Bulk calls take up to 1,000 ids and are all-or-nothing.

Collections are shared, named lists of entries. They can be pulled as a unit with a pull job.

Method and path Body Response
GET /collections?limit=&cursor= {"collections": [collection], "next_cursor"} (opaque cursor)
POST /collections {"name", "description"?} 201 collection
GET /collections/{collection_id} Collection plus entries: [{id, name, entry_type, category, status, has_thumbnail, added_at}]
PATCH /collections/{collection_id} {"name"?, "description"?} Collection
DELETE /collections/{collection_id} 204; entries are not deleted
PUT /collections/{collection_id}/entries/{entry_id} 204; idempotent
DELETE /collections/{collection_id}/entries/{entry_id} 204; idempotent
POST /collections/{collection_id}/entries/bulk {"entry_ids", "action": "add" or "remove"} {"collection_id", "action", "affected"}

A collection:

{
"id": 4,
"name": "Warehouse set dressing",
"description": "Approved props for the warehouse level",
"created_by": 7,
"created_at": "2026-07-20T10:00:00Z",
"updated_by": 7,
"date_updated": "2026-07-21T15:30:00Z",
"entry_count": 38
}

Names are 1 to 200 characters and unique (409 CONFLICT). Descriptions are up to 4,000 characters; null clears.

A pack records where a group of props came from (a marketplace pack, an outsourcing delivery). Pushes create packs automatically; you can also manage them directly.

Method and path Body Response
GET /packs?limit=&cursor= {"packs": [pack], "next_cursor"} (opaque cursor)
POST /packs {"name", "source_path"?, "source_type"?, "engine_version"?, "product_url"?, "notes"?} 201 pack
GET /packs/{pack_id} Pack
PATCH /packs/{pack_id} Any of the create fields Pack
DELETE /packs/{pack_id} Admin. See below.
{
"id": 3,
"name": "Industrial Yard",
"source_path": null,
"source_type": "uasset",
"engine_version": "5.8.0",
"product_url": "https://www.fab.com/listings/<listing>",
"notes": null,
"created_by": 7,
"date_added": "2026-07-12T18:00:00Z",
"updated_by": 7,
"date_updated": "2026-07-12T18:00:00Z",
"entry_count": 214,
"object_count": 603
}

name is unique (409 CONFLICT). source_type defaults to uasset (up to 50 characters). entry_count counts entries whose current version came from this pack.

DELETE /packs/{pack_id} (Admin) removes the pack, its entries, and its versions that nothing else uses. It refuses with 409 CONFLICT if any of the pack’s entries was ever downloaded or pulled. Returns {"pack_id", "name", "deleted_entries", "deleted_objects", "kept_shared_objects", "storage"}.

Projects are the list of Unreal project names that pull jobs target. Creating a pull job for a new project name adds it automatically.

Method and path Body Response
GET /projects {"projects": [project]}, most recently used first
POST /projects {"name", "default_root"?, "notes"?} 201 project
PATCH /projects/{project_id} Any of the create fields Project
DELETE /projects/{project_id} 204; existing pull jobs are unaffected
{
"id": 2,
"name": "ColdStorage",
"default_root": "/Game/Prophouse",
"notes": null,
"created_by": 7,
"created_at": "2026-07-10T09:00:00Z",
"last_used_at": "2026-07-25T12:00:00Z",
"job_count": 14
}

name is unique regardless of case (409 CONFLICT). default_root must be /Game or a folder under it (segments of letters, digits, _ and -). An unknown id is 404 NOT_FOUND.