Pull jobs
A pull job asks the Unreal plugin to bring props into a project with every dependency, laid out under a target folder. You create the job; an Unreal editor open on that project claims it, downloads the files, imports them and reports back.
| Method and path | Scope | Who calls it |
|---|---|---|
POST /pull-jobs |
write | You: create a job |
GET /pull-jobs |
read | You or the editor: list jobs |
GET /pull-jobs/{job_id} |
read | Job detail and report |
PATCH /pull-jobs/{job_id} with cancelled |
write | You: cancel a queued job |
POST /pull-jobs/{job_id}/claim |
write | The editor |
POST /pull-jobs/{job_id}/heartbeat |
write | The editor |
GET /pull-jobs/{job_id}/closure |
read | The editor |
PATCH /pull-jobs/{job_id} with running, done, failed |
write | The editor |
POST /pull-jobs
Section titled “POST /pull-jobs”curl -X POST -H "Authorization: Bearer $PH_KEY" -H "Content-Type: application/json" \ -d '{"target_project": "ColdStorage", "target_root": "/Game/Prophouse", "entries": [{"entry_id": 412}, {"entry_id": 88, "object_id": 1502}]}' \ https://api.prophouse.dev/api/v1/pull-jobs| Field | Type | Description |
|---|---|---|
target_project |
string, required | The Unreal project name the job is for (up to 200 characters). Only an editor open on a project with this exact name can claim it. |
target_root |
string | Destination folder: /Game or a folder below it. Default /Game/Prophouse. |
entries |
array | [{entry_id, object_id?}], up to 1,000. object_id pins a specific version from the entry’s lineage; omit it for the current version. |
collection_id |
integer | Pull every entry in a collection instead. Send exactly one of entries or collection_id. |
delivery |
string | manual (default): the editor picks the job up on its next check or when someone clicks to check now. agent: delivered automatically to your own editors that have agent imports turned on. See Editor sessions. |
The full dependency closure is resolved and saved when the job is created, so later pushes do not change what a queued job delivers. Creating a job marks its entries as pulled (they can no longer be hard-deleted) and records download.pull activity. A new target_project name is added to projects.
201 returns the job detail plus warnings, for example {"type": "deprecated_entry", ...} for deprecated entries (they are still pulled).
| Error | Cause |
|---|---|
404 ENTRY_NOT_FOUND, 404 OBJECT_NOT_FOUND, 404 COLLECTION_NOT_FOUND |
Unknown id. |
409 LINEAGE_CONFLICT |
A pinned object_id is not in that entry’s lineage. |
422 VALIDATION |
Bad target_root; the closure includes files without a /Game path (source files cannot be pulled into Unreal); the closure has more than 5,000 files; two versions of one prop would land in the same place. |
Job payload
Section titled “Job payload”{ "id": 57, "target_project": "ColdStorage", "target_root": "/Game/Prophouse", "requested_by": 7, "claimed_by": null, "status": "queued", "delivery": "manual", "failure_reason": null, "engine_version": null, "entry_count": 2, "object_count": 9, "total_bytes": 18874368, "created_at": "2026-07-25T12:00:00Z", "updated_at": "2026-07-25T12:00:00Z"}status is queued, claimed, running, done, failed or cancelled. engine_version is the claiming editor’s engine version.
The detail (GET /pull-jobs/{job_id}) adds entries: [{entry_id, object_id, name, entry_type, status}] and report_json (the editor’s report once finished, otherwise null).
GET /pull-jobs
Section titled “GET /pull-jobs”| Parameter | Type | Description |
|---|---|---|
target_project |
string | Exact project name. |
status |
string | One status value. |
requested_by |
integer | Library user id of the requester. |
limit, cursor |
Opaque cursor pagination, newest first. |
Returns {"jobs": [job], "next_cursor"}.
Cancel a job
Section titled “Cancel a job”{ "status": "cancelled" }PATCH /pull-jobs/{job_id}. Only the requester or an admin (403 otherwise), and only while the job is queued (409 JOB_STATE otherwise).
Claim lifecycle
Section titled “Claim lifecycle”POST /pull-jobs/{job_id}/claimwith{"project_key": "<target_project>", "engine_version": "5.8.0"}. Exactly one claimant wins. The response is the job detail plus a secretclaim_tokenandwarnings(for exampleolder_enginefor files saved by an older engine). Claims are leases of 60 minutes.GET /pull-jobs/{job_id}/closurereturns the saved file list:{"job_id", "objects": [{object_id, content_hash, lineage_id, asset_class, asset_name, origin_path, blob_size, engine_version}], "total_objects", "total_bytes"}. Download each through Downloads.PATCH /pull-jobs/{job_id}with{"status": "running", "claim_token"}.POST /pull-jobs/{job_id}/heartbeatwith{"claim_token"}during long work. Returns{"job_id", "status", "lease_expires_at"}.- Finish with
PATCH{"status": "done", "claim_token", "report_json"}or{"status": "failed", "claim_token", "failure_reason", "report_json"?}.
Claim errors: 409 CONFLICT when project_key is not the job’s target_project; 409 JOB_STATE when the job is no longer queued; 409 ENGINE_TOO_OLD when some files were saved by a newer engine than yours (details.objects, the job stays queued). If a lease expires, the job returns to queued and the old claim_token stops working (409 JOB_STATE).
The report lists every closure file exactly once:
{ "added": [{ "asset_name": "SM_Barrel_Rusty", "destination": "/Game/Prophouse/Meshes/SM_Barrel_Rusty", "content_hash": "9f2c...e41a", "lineage_id": "guid:6F96...64FF", "action": "add" }], "skipped_identical": [], "conflicts": [], "failed": []}Every item has exactly asset_name, destination, content_hash and lineage_id, plus: action (add or safe_overwrite) on added items; conflict items carry resolution (keep_local, overwrite or skip); failed items carry error. A done report must account for every closure file and contain no failed items. Reports are limited to 5,000 items and 2 MiB, and unknown fields are rejected (422).
