---
title: "Workflows API"
description: "REST API endpoints for managing item status workflows in TraceRTM"
---

# Workflows API

Workflows define the lifecycle states and transitions for items in TraceRTM. They control how items move through statuses like Draft, Review, and Approved.

## Endpoints Overview

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/v1/projects/{project_id}/workflows` | List workflows |
| `POST` | `/v1/projects/{project_id}/workflows` | Create workflow |
| `GET` | `/v1/workflows/{id}` | Get workflow details |
| `PATCH` | `/v1/workflows/{id}` | Update workflow |
| `DELETE` | `/v1/workflows/{id}` | Delete workflow |
| `POST` | `/v1/items/{id}/transition` | Transition item status |
| `GET` | `/v1/items/{id}/transitions` | Get available transitions |

## Workflow Object

```json
{
  "id": "wf_standard_req",
  "project_id": "proj_456",
  "name": "Requirement Workflow",
  "description": "Standard workflow for requirements approval",
  "item_types": ["requirement"],
  "statuses": [
    {
      "id": "draft",
      "name": "Draft",
      "color": "#6B7280",
      "category": "todo",
      "initial": true
    },
    {
      "id": "review",
      "name": "In Review",
      "color": "#F59E0B",
      "category": "in_progress"
    },
    {
      "id": "approved",
      "name": "Approved",
      "color": "#10B981",
      "category": "done"
    },
    {
      "id": "rejected",
      "name": "Rejected",
      "color": "#EF4444",
      "category": "done"
    },
    {
      "id": "obsolete",
      "name": "Obsolete",
      "color": "#9CA3AF",
      "category": "done"
    }
  ],
  "transitions": [
    {
      "id": "trans_1",
      "from": "draft",
      "to": "review",
      "name": "Submit for Review",
      "conditions": [],
      "actions": []
    },
    {
      "id": "trans_2",
      "from": "review",
      "to": "approved",
      "name": "Approve",
      "conditions": [
        {"type": "permission", "value": "approve:requirements"}
      ],
      "actions": [
        {"type": "notify", "target": "assignee"}
      ]
    },
    {
      "id": "trans_3",
      "from": "review",
      "to": "rejected",
      "name": "Reject",
      "conditions": [
        {"type": "permission", "value": "approve:requirements"}
      ],
      "actions": [
        {"type": "require_comment", "message": "Please provide rejection reason"}
      ]
    },
    {
      "id": "trans_4",
      "from": "review",
      "to": "draft",
      "name": "Return to Draft",
      "conditions": []
    },
    {
      "id": "trans_5",
      "from": "approved",
      "to": "obsolete",
      "name": "Mark Obsolete",
      "conditions": []
    }
  ],
  "settings": {
    "allow_any_to_draft": true,
    "require_comment_on_rejection": true,
    "lock_on_approval": false
  },
  "created_at": "2025-01-01T10:00:00Z",
  "updated_at": "2025-01-10T15:00:00Z"
}
```

## Status Categories

| Category | Description | Typical Use |
|----------|-------------|-------------|
| `todo` | Not started | Draft, Backlog |
| `in_progress` | Work ongoing | In Review, In Development |
| `done` | Completed | Approved, Closed |
| `blocked` | Cannot proceed | Blocked, On Hold |

## List Workflows

Get all workflows for a project.

```http
GET /v1/projects/{project_id}/workflows
```

### Request

```bash
curl -X GET "https://api.tracertm.dev/v1/projects/proj_456/workflows" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"
```

### Response

```json
{
  "data": [
    {
      "id": "wf_standard_req",
      "name": "Requirement Workflow",
      "item_types": ["requirement"],
      "status_count": 5,
      "transition_count": 5,
      "is_default": true
    },
    {
      "id": "wf_test_case",
      "name": "Test Case Workflow",
      "item_types": ["test_case"],
      "status_count": 6,
      "transition_count": 8,
      "is_default": false
    },
    {
      "id": "wf_bug",
      "name": "Bug Workflow",
      "item_types": ["bug"],
      "status_count": 7,
      "transition_count": 10,
      "is_default": false
    }
  ]
}
```

### Python Example

```python
from tracertm import TraceRTMClient

client = TraceRTMClient(api_key="rtm_live_...")

# List all workflows
workflows = await client.workflows.list(project_id="proj_456")

for wf in workflows:
    print(f"{wf.name}: {wf.status_count} statuses, {wf.transition_count} transitions")

# Get workflow for specific item type
req_workflow = await client.workflows.get_for_type(
    project_id="proj_456",
    item_type="requirement"
)
```

## Create Workflow

Create a custom workflow for items.

```http
POST /v1/projects/{project_id}/workflows
```

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Workflow name |
| `description` | string | No | Workflow description |
| `item_types` | array | Yes | Item types this applies to |
| `statuses` | array | Yes | Status definitions |
| `transitions` | array | Yes | Allowed transitions |
| `settings` | object | No | Workflow settings |

### Request

```bash
curl -X POST "https://api.tracertm.dev/v1/projects/proj_456/workflows" \
  -H "Authorization: Bearer $TRACERTM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Agile Story Workflow",
    "description": "Workflow for user stories with sprint support",
    "item_types": ["user_story"],
    "statuses": [
      {"id": "backlog", "name": "Backlog", "color": "#6B7280", "category": "todo", "initial": true},
      {"id": "ready", "name": "Ready", "color": "#3B82F6", "category": "todo"},
      {"id": "in_progress", "name": "In Progress", "color": "#F59E0B", "category": "in_progress"},
      {"id": "in_review", "name": "In Review", "color": "#8B5CF6", "category": "in_progress"},
      {"id": "done", "name": "Done", "color": "#10B981", "category": "done"},
      {"id": "blocked", "name": "Blocked", "color": "#EF4444", "category": "blocked"}
    ],
    "transitions": [
      {"from": "backlog", "to": "ready", "name": "Groom"},
      {"from": "ready", "to": "in_progress", "name": "Start Work"},
      {"from": "in_progress", "to": "in_review", "name": "Submit for Review"},
      {"from": "in_review", "to": "done", "name": "Complete"},
      {"from": "in_review", "to": "in_progress", "name": "Request Changes"},
      {"from": "*", "to": "blocked", "name": "Block"},
      {"from": "blocked", "to": "in_progress", "name": "Unblock"}
    ],
    "settings": {
      "allow_any_to_backlog": true
    }
  }'
```

### Response

```json
{
  "data": {
    "id": "wf_agile_story",
    "project_id": "proj_456",
    "name": "Agile Story Workflow",
    "description": "Workflow for user stories with sprint support",
    "item_types": ["user_story"],
    "statuses": [...],
    "transitions": [...],
    "created_at": "2025-01-15T16:00:00Z"
  }
}
```

### Python Example

```python
from tracertm.models import StatusCategory

# Create workflow
workflow = await client.workflows.create(
    project_id="proj_456",
    name="Agile Story Workflow",
    item_types=["user_story"],
    statuses=[
        {"id": "backlog", "name": "Backlog", "category": StatusCategory.TODO, "initial": True},
        {"id": "ready", "name": "Ready", "category": StatusCategory.TODO},
        {"id": "in_progress", "name": "In Progress", "category": StatusCategory.IN_PROGRESS},
        {"id": "in_review", "name": "In Review", "category": StatusCategory.IN_PROGRESS},
        {"id": "done", "name": "Done", "category": StatusCategory.DONE},
        {"id": "blocked", "name": "Blocked", "category": StatusCategory.BLOCKED}
    ],
    transitions=[
        {"from": "backlog", "to": "ready", "name": "Groom"},
        {"from": "ready", "to": "in_progress", "name": "Start"},
        {"from": "in_progress", "to": "in_review", "name": "Submit"},
        {"from": "in_review", "to": "done", "name": "Complete"},
        {"from": "in_review", "to": "in_progress", "name": "Request Changes"},
        {"from": "*", "to": "blocked", "name": "Block"},
        {"from": "blocked", "to": "in_progress", "name": "Unblock"}
    ]
)
```

## Transition Item

Move an item to a new status.

```http
POST /v1/items/{id}/transition
```

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | string | Yes | Target status ID |
| `comment` | string | No | Transition comment |
| `resolution` | string | No | Resolution for closing statuses |
| `assignee_id` | string | No | Change assignee during transition |

### Request

```bash
curl -X POST "https://api.tracertm.dev/v1/items/item_req001/transition" \
  -H "Authorization: Bearer $TRACERTM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "approved",
    "comment": "Reviewed and approved by product team. All acceptance criteria are clear and testable."
  }'
```

### Response

```json
{
  "data": {
    "item_id": "item_req001",
    "previous_status": "review",
    "new_status": "approved",
    "transition": {
      "id": "trans_2",
      "name": "Approve"
    },
    "comment": "Reviewed and approved by product team...",
    "transitioned_by": "user_xyz789",
    "transitioned_at": "2025-01-15T16:30:00Z",
    "item": {
      "id": "item_req001",
      "custom_id": "REQ-001",
      "title": "User Authentication",
      "status": "approved",
      "version": 6
    }
  }
}
```

### Python Example

```python
# Simple transition
result = await client.items.transition(
    item_id="item_req001",
    status="approved",
    comment="Approved after security review"
)
print(f"Transitioned from {result.previous_status} to {result.new_status}")

# Transition with assignee change
await client.items.transition(
    item_id="item_story001",
    status="in_progress",
    assignee_id="user_dev123",
    comment="Starting development"
)

# Reject with required comment
await client.items.transition(
    item_id="item_req002",
    status="rejected",
    comment="Requirements are unclear. Please add more detail to acceptance criteria.",
    resolution="needs_clarification"
)

# Bulk transition
await client.items.bulk_transition(
    items=[
        {"id": "item_1", "status": "approved"},
        {"id": "item_2", "status": "approved"},
        {"id": "item_3", "status": "approved"}
    ],
    comment="Bulk approval after sprint review"
)
```

### TypeScript Example

```typescript
import { TraceRTMClient } from '@tracertm/sdk'

const client = new TraceRTMClient({ apiKey: 'rtm_live_...' })

// Transition item
const result = await client.items.transition({
  itemId: 'item_req001',
  status: 'approved',
  comment: 'Approved after review'
})

console.log(`${result.previousStatus} -> ${result.newStatus}`)

// With assignee change
await client.items.transition({
  itemId: 'item_story001',
  status: 'in_progress',
  assigneeId: 'user_dev123'
})
```

## Get Available Transitions

Get allowed transitions for an item's current status.

```http
GET /v1/items/{id}/transitions
```

### Request

```bash
curl -X GET "https://api.tracertm.dev/v1/items/item_req001/transitions" \
  -H "Authorization: Bearer $TRACERTM_API_KEY"
```

### Response

```json
{
  "data": {
    "item_id": "item_req001",
    "current_status": {
      "id": "review",
      "name": "In Review"
    },
    "available_transitions": [
      {
        "id": "trans_2",
        "to_status": "approved",
        "name": "Approve",
        "allowed": true,
        "conditions_met": true
      },
      {
        "id": "trans_3",
        "to_status": "rejected",
        "name": "Reject",
        "allowed": true,
        "conditions_met": true,
        "requires": ["comment"]
      },
      {
        "id": "trans_4",
        "to_status": "draft",
        "name": "Return to Draft",
        "allowed": true
      }
    ],
    "blocked_transitions": [
      {
        "id": "trans_5",
        "to_status": "obsolete",
        "name": "Mark Obsolete",
        "allowed": false,
        "reason": "Can only transition from 'approved' status"
      }
    ]
  }
}
```

### Python Example

```python
# Get available transitions
transitions = await client.items.get_transitions("item_req001")

print(f"Current status: {transitions.current_status.name}")
print("Available transitions:")
for t in transitions.available:
    print(f"  -> {t.to_status}: {t.name}")
    if t.requires:
        print(f"    Requires: {', '.join(t.requires)}")

# Check if specific transition is allowed
can_approve = await client.items.can_transition(
    item_id="item_req001",
    to_status="approved"
)
```

## Transition Conditions

Transitions can have conditions that must be met:

### Permission Condition

```json
{
  "type": "permission",
  "value": "approve:requirements"
}
```

### Field Condition

```json
{
  "type": "field_required",
  "field": "assignee_id",
  "message": "Item must be assigned before review"
}
```

### Link Condition

```json
{
  "type": "has_links",
  "link_type": "verified_by",
  "min_count": 1,
  "message": "Requirement must have at least one test case"
}
```

### Custom Condition

```json
{
  "type": "custom",
  "webhook_url": "https://your-app.com/validate-transition",
  "timeout_seconds": 5
}
```

## Transition Actions

Actions that run when a transition occurs:

### Notify Action

```json
{
  "type": "notify",
  "target": "assignee",
  "template": "item_status_changed"
}
```

### Require Comment

```json
{
  "type": "require_comment",
  "message": "Please explain this transition"
}
```

### Set Field

```json
{
  "type": "set_field",
  "field": "resolved_at",
  "value": "{{now}}"
}
```

### Webhook

```json
{
  "type": "webhook",
  "url": "https://your-app.com/on-transition",
  "method": "POST"
}
```

## Workflow Templates

Get pre-built workflow templates.

```http
GET /v1/workflow-templates
```

### Response

```json
{
  "data": [
    {
      "id": "tpl_standard",
      "name": "Standard Approval",
      "description": "Simple draft -> review -> approved workflow",
      "item_types": ["requirement", "user_story"],
      "status_count": 4
    },
    {
      "id": "tpl_agile",
      "name": "Agile Kanban",
      "description": "Backlog -> Ready -> In Progress -> Done",
      "item_types": ["user_story", "task"],
      "status_count": 5
    },
    {
      "id": "tpl_bug_tracking",
      "name": "Bug Tracking",
      "description": "Open -> Triaged -> In Progress -> Fixed -> Verified -> Closed",
      "item_types": ["bug"],
      "status_count": 7
    },
    {
      "id": "tpl_test_execution",
      "name": "Test Execution",
      "description": "Not Run -> Running -> Passed/Failed/Blocked",
      "item_types": ["test_case"],
      "status_count": 5
    }
  ]
}
```

### Create from Template

```python
# Create workflow from template
workflow = await client.workflows.create_from_template(
    project_id="proj_456",
    template_id="tpl_agile",
    name="Sprint Workflow",
    customizations={
        "add_status": {"id": "qa", "name": "QA", "after": "in_progress"},
        "add_transition": {"from": "in_progress", "to": "qa", "name": "Send to QA"}
    }
)
```

## Error Responses

### Invalid Transition (400)

```json
{
  "error": {
    "code": "invalid_transition",
    "message": "Cannot transition from 'draft' to 'approved'",
    "details": {
      "current_status": "draft",
      "requested_status": "approved",
      "allowed_transitions": ["review"]
    }
  }
}
```

### Condition Not Met (400)

```json
{
  "error": {
    "code": "condition_not_met",
    "message": "Transition conditions not satisfied",
    "details": {
      "failed_conditions": [
        {
          "type": "has_links",
          "message": "Requirement must have at least one test case linked"
        }
      ]
    }
  }
}
```

### Comment Required (400)

```json
{
  "error": {
    "code": "comment_required",
    "message": "This transition requires a comment",
    "details": {
      "transition": "Reject"
    }
  }
}
```

## Webhooks

### Transition Events

| Event | Description |
|-------|-------------|
| `item.transitioned` | Item status changed |
| `item.transition_blocked` | Transition was blocked |
| `workflow.updated` | Workflow definition changed |

### Webhook Payload

```json
{
  "event": "item.transitioned",
  "timestamp": "2025-01-15T16:30:00Z",
  "data": {
    "item": {
      "id": "item_req001",
      "custom_id": "REQ-001",
      "title": "User Authentication"
    },
    "transition": {
      "from": "review",
      "to": "approved",
      "name": "Approve"
    },
    "comment": "Approved after review",
    "transitioned_by": "user_xyz789"
  }
}
```

## Best Practices

### Design Clear Workflows

```python
# Good - clear status names and progression
statuses = [
    {"id": "draft", "name": "Draft", "category": "todo"},
    {"id": "ready_for_review", "name": "Ready for Review", "category": "todo"},
    {"id": "in_review", "name": "In Review", "category": "in_progress"},
    {"id": "approved", "name": "Approved", "category": "done"},
    {"id": "rejected", "name": "Rejected", "category": "done"}
]

# Avoid - confusing status names
statuses = [
    {"id": "new", "name": "New"},
    {"id": "wip", "name": "WIP"},  # Unclear
    {"id": "done", "name": "Done"},  # Too generic
]
```

### Use Transition Conditions

```python
# Ensure quality gates
transitions = [
    {
        "from": "in_review",
        "to": "approved",
        "name": "Approve",
        "conditions": [
            {"type": "permission", "value": "approve:requirements"},
            {"type": "has_links", "link_type": "verified_by", "min_count": 1}
        ]
    }
]
```

### Track Transitions

```python
# Log all transitions
async def on_transition(item_id: str, from_status: str, to_status: str):
    await client.items.add_comment(
        item_id=item_id,
        content=f"Status changed: {from_status} -> {to_status}",
        system=True
    )
```

## Related Documentation

- [Items API](/docs/api-reference/rest-api/items) - Item management
- [Projects API](/docs/api-reference/rest-api/projects) - Project settings
- [Webhooks](/docs/api-reference/rest-api/webhooks) - Event notifications
- [Workflow Concepts](/docs/wiki/concepts/workflows) - Understanding workflows

