Skip to main content

Projects

A project is an isolated dataset workspace. Each project has its own OpenSearch indexes, its own directories, its own class registry, its own prompt packs, region profiles, keymap and active VLM endpoint. A request is bound to exactly one project by its URL, and the storage layer refuses any access that would reach another project's indexes.

API=http://localhost:4603 is assumed in every example.

Concepts​

  • Slug. The project id in URLs. 2 to 32 characters, lowercase letters and digits with single hyphens between segments, starting with a letter (^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$). The words all, combine, global, health, new, none, projects, settings and vlm are reserved. The slug cannot be changed. The slug of a deleted project is retired and cannot be reused.
  • default project. Created at bootstrap like any other project. It can be archived but never deleted.
  • Status. One of building, active, archived, deleting, deleted, failed. Only active projects are writable. An archived project stays readable and refuses every write. building, deleting and failed projects cannot be bound at all.
  • Revision. Every lifecycle change bumps revision. PATCH, archive and unarchive require expected_revision, so two clients cannot overwrite each other.
  • Per-project resources. Fixed when the project is created and never recomputed from a later environment change.
ResourceLocation
Indexes<OP_PROJECT_INDEX_PREFIX><slug>__<role> (default prefix op_prj_)
Class registry<OP_PROJECTS_DATA_ROOT>/<slug>/class_registry.json
Exports<OP_PROJECTS_DATA_ROOT>/<slug>/exports/
Bake-off eval sets<OP_PROJECTS_DATA_ROOT>/<slug>/bakeoff_eval/
Uploads, bake-off jobs, per-project state<OP_STATE_DIR>/projects/<slug>/
Training jobs<OP_TRAIN_JOBS_DIR>/projects/<slug>/
Auto-label state<OP_AUTO_LABEL_STATE_DIR>/projects/<slug>/
MLflow experimentopenprocessor-<slug>
Promoted model prefix<slug>__ (the default project uses no prefix)

GET /curation/projects/{project} returns the resolved resources for a project.

Routes​

CallPurpose
GET /curation/projectsList projects, limits and shard capacity
POST /curation/projectsCreate a project
GET /curation/projects/{project}One project record with resources
PATCH /curation/projects/{project}Change display name or description
POST /curation/projects/{project}/archiveMake the project read-only
POST /curation/projects/{project}/unarchiveMake it writable again
DELETE /curation/projects/{project}Dry run, or guarded delete
POST /curation/projects/{project}/clone_settingsCopy settings from another project
POST /curation/projects/{project}/pauseWorkers skip this project
POST /curation/projects/{project}/resumeClear the pause flag

List​

curl -s "$API/curation/projects?include_archived=true"

The response has projects (each with slug, display_name, description, status, revision, is_default, counts, writable, selectable, archivable, unarchivable, deletable, paused, origin), default_slug, limits (slug pattern and length, reserved and retired slugs, cloneable axes), labels (display text for statuses) and capacity. capacity reports the OpenSearch shard budget: status is ok, warn or blocked.

Create​

curl -s -X POST "$API/curation/projects" \
-H 'Content-Type: application/json' \
-d '{
"slug": "cars",
"display_name": "Cars and wheels",
"description": "Public car photos",
"clone_settings_from": null,
"clone_axes": null
}'

Returns 201 with project, warnings and keymap_clone_conflicts. A warnings entry with code shard_budget_high means the cluster is close to its shard limit. Creation runs these steps: validate the slug, check capacity, write a building record, create the indexes and directories, optionally clone settings, then flip to active. A failure midway leaves the record failed, never partly active.

clone_settings_from names an existing active or archived project to copy from. clone_axes limits what is copied. Valid axes are settings_defaults, classes, activations, keymap, prompt_packs and vlm_activation. null or an empty list means every axis. See Settings and configuration.

Edit, archive, unarchive​

# revision comes from the last response
curl -s -X PATCH "$API/curation/projects/cars" \
-H 'Content-Type: application/json' \
-d '{"expected_revision": 3, "display_name": "Cars", "description": "Public car photos"}'

curl -s -X POST "$API/curation/projects/cars/archive" \
-H 'Content-Type: application/json' -d '{"expected_revision": 4}'

curl -s -X POST "$API/curation/projects/cars/unarchive" \
-H 'Content-Type: application/json' -d '{"expected_revision": 5}'

Each answers 200 with the lifecycle envelope (project, warnings, keymap_clone_conflicts). Archive is refused while the project has running jobs, and for the last active project.

Delete​

Delete is two-phase and guarded. First a dry run, which writes nothing:

curl -s -X DELETE "$API/curation/projects/cars?dry_run=true"

The 200 response lists indexes (name, doc count, size), dirs (path, bytes), mlflow_experiment, promoted_models, running_jobs, referenced_by (other projects whose active detection profile uses one of its shared models) and blocking / blocking_detail, the reasons a real delete would be refused.

Then the real delete. confirm must equal the slug:

curl -s -X DELETE "$API/curation/projects/cars?confirm=cars"

The response is 202 with the record in status deleting. Index and directory removal run in the background. Poll GET /curation/projects/{project} until it answers 404, or follow the project.deleted event on the global event stream (GET /curation/events). A repeat call while it is deleting also answers 202 deleting. A failed finish leaves the record deleting; repeat the same call with confirm to retry.

A deleted slug is retired: creating a project with the same slug afterwards answers slug_retired, so scripts must use a fresh slug each run.

force=true skips the refusal for models that other projects share. It does not skip the busy check, the last-active check or the default protection.

Pause and resume​

curl -s -X POST "$API/curation/projects/cars/pause"
curl -s "$API/curation/projects/cars/pause"
curl -s -X POST "$API/curation/projects/cars/resume"

A paused project is skipped in every worker's next discovery cycle. Both calls are idempotent. The response has paused, paused_by and reason.

Errors​

Every error body has the shape {"detail": {"error": "<code>", "message": "...", ...}}.

StatuserrorWhen
404project_not_foundUnknown or deleted slug
409project_building, project_deleting, project_failedProject is in a state that cannot be bound
409project_archivedWrite to an archived project
409project_read_onlyWrite while the registry cannot be confirmed fresh
409slug_taken, slug_retiredCreate with a used or retired slug
409revision_conflictexpected_revision is stale; current_revision is returned
409invalid_transitionFor example archiving a project that is not active
409project_busyRunning jobs; jobs lists them and the message names them. A detection worker finishing its last batch clears within about 15 seconds, so retry
409last_active_projectArchive or delete would leave no active project
409project_protectedDelete of the default project
409in_useDelete while another project depends on a shared model
409shard_budget_exceededCreate blocked; the full capacity block is returned
409clone_source_not_readyClone source is not active or archived
409target_not_emptyClone into a project that already has data
422slug_invalidSlug fails the pattern, length or reserved list
422confirm_mismatchReal delete without confirm equal to the slug
422combine_invalidUnknown clone axis, or clone into itself

There is no dedicated project subcommand in the openprocessor CLI. Manage projects through the API as above. Per-project environment values are listed in Basic configuration.

Cropwright​

The project switcher (captured from Cropwright against the public COCO sample projects)
The create-project form (captured from Cropwright against the public COCO sample projects)
Copying a project's settings into a new project (captured from Cropwright against the public COCO sample projects)

See also​