Check-out locks
A check-out lock tells your team “I am working on this prop.” While you hold it, pushes by anyone else that would create a new version of that prop are held back with status locked; your own pushes go through. Restoring an earlier version (POST /entries/{entry_id}/restore) or changing the current version with PATCH /entries/{entry_id} object_id is refused with 409 LOCKED for anyone but the holder or an admin. Locks are per lineage (all versions of one prop) and expire on their own.
| Method and path | Scope | Purpose |
|---|---|---|
POST /locks |
write | Check out (or extend your own lock) |
DELETE /locks/{lock_id} |
write | Release |
GET /locks |
read | List locks |
POST /locks
Section titled “POST /locks”curl -X POST -H "Authorization: Bearer $PH_KEY" -H "Content-Type: application/json" \ -d '{"lineage_id": "guid:6F9619FF8B86D011B42D00C04FC964FF", "note": "Reworking LODs", "ttl_hours": 48}' \ https://api.prophouse.dev/api/v1/locks| Field | Type | Description |
|---|---|---|
lineage_id |
string, required | The entry’s lineage_id. |
note |
string | Up to 1,000 characters, shown to teammates. |
ttl_hours |
integer | 1 to 720. Default 72. |
201 response:
{ "lock": { "id": 31, "lineage_id": "guid:6F9619FF8B86D011B42D00C04FC964FF", "holder": { "id": 7, "name": "<user>", "display_name": "Sam Rigger" }, "note": "Reworking LODs", "base_object_id": 1880, "created_at": "2026-07-25T12:00:00Z", "expires_at": "2026-07-27T12:00:00Z", "released_at": null, "active": true }, "checkout": { "entry_id": 412, "object_id": 1880, "content_hash": "9f2c...e41a", "blob_url": "/api/v1/blobs/9f2c...e41a", "suggested_filename": "SM_Barrel_Rusty" }}checkout tells you what to edit: the current version, its hash, where to download it (blob_url is an API path; or use GET /blobs/{content_hash}/url, see Downloads), and a filename that includes the original extension for source files (Barrel.fbx).
The lock’s base_object_id is the version you checked out: the checkout.object_id at the time the lock was created. It moves when you push a new version, restore a version or change the current version yourself while holding the lock. It does not move when someone else changes the current version (an admin restore, for example), so you can compare it with the entry’s current object_id to tell whether your starting point is stale. Locks created before this field existed have base_object_id: null.
Calling POST /locks again for a lineage you already hold extends it to the new ttl_hours (and replaces the note if you send one), keeping the same lock id and the original base_object_id.
| Error | Cause |
|---|---|
409 LOCKED |
Someone else holds an active lock. details.holder says who; details.lock is their lock. |
404 LINEAGE_NOT_FOUND |
No such lineage. |
Check in
Section titled “Check in”There is no separate check-in call. Push the edited file as a new version with stamped_lineage_id, stamped_base_hash and expected_face_object_id (the lock’s base_object_id) from the checkout (see Push a new version), then release the lock.
DELETE /locks/{lock_id}
Section titled “DELETE /locks/{lock_id}”Releases the lock. Returns 204. Only the holder or an admin can release (403 FORBIDDEN otherwise). Releasing an already released lock is a no-op; an unknown id is 404 NOT_FOUND.
GET /locks
Section titled “GET /locks”| Parameter | Type | Description |
|---|---|---|
lineage_id |
string | Only locks on this lineage. |
active |
boolean | true: only live locks. false: only released or expired ones. |
limit |
integer | 1 to 200, default 50. No cursor. |
Returns {"locks": [lock]}, newest first. A lock past its expires_at shows active: false even before it is formally released.
Lock changes also appear in the catalog change feed (entity lock) and as lock.acquire, lock.release and lock.expire activity events.
