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.
Entries
Section titled “Entries”| 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) |
PATCH /entries/{entry_id}
Section titled “PATCH /entries/{entry_id}”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.
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/412POST /entries/category/bulk
Section titled “POST /entries/category/bulk”{ "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"}.
POST /entries/variant-group/bulk
Section titled “POST /entries/variant-group/bulk”{ "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.
DELETE /entries/{entry_id} (Admin)
Section titled “DELETE /entries/{entry_id} (Admin)”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
Section titled “Collections”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
Section titled “Projects”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.
