Create and deploy a workflow
This page walks through deploying a workflow so you can call the run API.
Option A — Use the web UI
Section titled “Option A — Use the web UI”- Open your project.
- Open Create Workflow / Workflows (deployment UI).
- Choose the registered model(s) to bind.
- Upload or paste
workflow.py. - Configure optional routes (for example Dataset / Raw Data).
- Activate the workflow.
- Copy the generated cURL / Python snippet from the UI.
Screenshot: Create Workflow page — model binding and activate toggle
Option B — REST API (dev-style runbook)
Section titled “Option B — REST API (dev-style runbook)”Set variables:
BASE="https://api.development.labelty.ai/api/v1"KEY="lty_xxx"H=(-H "Authorization: Bearer $KEY" -H "Content-Type: application/json")
PID=$(curl -s "$BASE/projects/current" "${H[@]}" \ | python -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")What the projects/current call does: resolves the project UUID bound to your API key. Deploy paths still include project_id; the run path for API keys does not.
1. Write workflow.py
Section titled “1. Write workflow.py”from app.sdk.workflow import WorkflowBase, WorkflowInput, WorkflowOutput# When packaged for users, the import may be published as labelty_sdk.workflow
class DefectChecker(WorkflowBase): def load(self, models, config): # Binding names must match deploy_config.models keys self.detector = models["detector"] self.threshold = config.get("threshold", 0.75)
def run(self, input: WorkflowInput) -> WorkflowOutput | None: preds = self.detector.predict(input.asset) kept = [ p for p in preds if (p.metadata or {}).get("confidence", 1.0) >= self.threshold ] out = WorkflowOutput(predictions=kept) if any((p.metadata or {}).get("label") == "crack" for p in kept): # Route name must match deploy_config.outputs out.route("critical") return outWhy two methods:
loadruns when the runner starts the workflow (attach models + config once).runexecutes per request on one image.
2. Create the deployment row
Section titled “2. Create the deployment row”POST /projects/{project_id}/workflows
curl -sX POST "$BASE/projects/$PID/workflows" "${H[@]}" -d '{ "name": "defect-checker", "version": "v1", "description": "Filter low-confidence defects; route cracks to review", "deploy_config": { "models": { "detector": "<ml_model_uuid>" }, "outputs": { "critical": { "type": "dataset" } }, "config": { "threshold": 0.75 } }}'Save data.id as WID.
outputs.*.type = "dataset" means out.route("critical") uploads the image + predictions into the project for human review (product copy: Raw Data, not the curated dataset until approved).
3. Upload workflow.py (presign → PUT → confirm)
Section titled “3. Upload workflow.py (presign → PUT → confirm)”UP=$(curl -sX POST "$BASE/projects/$PID/workflows/$WID/presigned-upload" "${H[@]}" \ -d '{"filename": "workflow.py", "content_type": "text/x-python"}')URL=$(echo "$UP" | jq -r '.data.upload_url')
curl -sX PUT "$URL" -H "Content-Type: text/x-python" --data-binary @workflow.py
curl -sX POST "$BASE/projects/$PID/workflows/$WID/confirm-upload" "${H[@]}" \ -d '{"filename": "workflow.py"}'Why three steps: Labelty never streams the Python file through the API process; you upload directly to object storage with a short-lived URL, then confirm so the workflow row records the artifact.
4. Activate
Section titled “4. Activate”curl -sX PATCH "$BASE/projects/$PID/workflows/$WID/active" "${H[@]}" \ -d '{"is_active": true}'A workflow runs only when it is active and has a confirmed artifact, and deploy_config.models references valid models.
Lifecycle helpers
Section titled “Lifecycle helpers”curl -s "$BASE/projects/$PID/workflows" "${H[@]}"curl -s "$BASE/projects/$PID/workflows/$WID" "${H[@]}"curl -sX PATCH "$BASE/projects/$PID/workflows/$WID/active" "${H[@]}" -d '{"is_active": false}'curl -sX DELETE "$BASE/projects/$PID/workflows/$WID" "${H[@]}"Next step
Section titled “Next step”Call the synchronous run endpoint: Call the run API.