Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Playbook — Running a Sprint (for PMs)

This playbook walks a project manager through running a complete sprint in Alloy, from backlog grooming through retrospective. Every step uses the API directly — you can script any of it, wire it into Slack, or call it through the MCP server. The guide assumes you already have an Alloy account, an organization, and a project with tickets.

For the full sprint API reference, see Sprints & Boards.

1. Prerequisites and Setup

Set shell variables so the examples are copy-pasteable:

BASE_URL="http://localhost:3000"
TOKEN="<your-access-token>"
PROJECT_ID="<your-project-uuid>"

Confirm you can reach the server:

curl -s "$BASE_URL/health" | jq .
{
  "status": "ok",
  "service": "alloy",
  "version": "0.1.0",
  "database": "...",
  "db_healthy": true,
  "migration_version": "...",
  "uptime_seconds": "..."
}

Check your identity:

curl -s "$BASE_URL/api/v1/auth/me" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "user_id": "...",
  "org_id": "...",
  "email": "...",
  "role": "..."
}

CLI shortcut: alloy auth me

2. Groom the Backlog

Before planning a sprint, review the backlog. List tickets in the project:

curl -s "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets?limit=20" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": "...",
  "next_cursor": null,
  "has_more": false
}

Update ticket priorities and descriptions as needed:

curl -s -X PATCH "$BASE_URL/api/v1/tickets/$TICKET_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"priority": "High"}' | jq .
{
  "id": "...",
  "project_id": "...",
  "ticket_number": "...",
  "title": "...",
  "status": "...",
  "priority": "High"
}

CLI shortcut: alloy ticket update $TICKET_ID --priority High

3. Create the Sprint

Define a two-week sprint with a clear goal:

curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/sprints" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Sprint 4 — Payments Integration",
    "goal": "Ship Stripe checkout and invoice generation",
    "start_date": "2026-04-01",
    "end_date": "2026-04-15"
  }' | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 4 — Payments Integration",
  "goal": "Ship Stripe checkout and invoice generation",
  "start_date": "2026-04-01",
  "end_date": "2026-04-15",
  "status": "Planned",
  "created_at": "...",
  "updated_at": "..."
}

Save the sprint ID:

SPRINT_ID="<id from above>"

A good sprint goal is specific and outcome-oriented. Avoid goals like “work on payments” — state what ships.

CLI shortcut: alloy sprint create --project $PROJECT_ID --name "Sprint 4" --goal "Ship Stripe checkout" --start-date 2026-04-01 --end-date 2026-04-15

4. Assign Owners

Every ticket in the sprint should have an assignee. Update a ticket’s assignee:

curl -s -X PATCH "$BASE_URL/api/v1/tickets/$TICKET_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"assignee_id\": \"$USER_ID\"}" | jq .
{
  "id": "...",
  "project_id": "...",
  "ticket_number": "...",
  "title": "...",
  "status": "...",
  "assignee_id": "..."
}

Repeat for each ticket. As a PM, make sure every ticket has an owner before the sprint starts — unowned tickets tend to drift.

CLI shortcut: alloy ticket update $TICKET_ID --assignee $USER_ID

5. Review the Sprint Scope

Before starting, fetch the sprint to confirm it looks right:

curl -s "$BASE_URL/api/v1/sprints/$SPRINT_ID" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 4 — Payments Integration",
  "goal": "Ship Stripe checkout and invoice generation",
  "start_date": "2026-04-01",
  "end_date": "2026-04-15",
  "status": "Planned",
  "created_at": "...",
  "updated_at": "..."
}

Check the sprint board to see what tickets are assigned:

curl -s "$BASE_URL/api/v1/sprints/$SPRINT_ID/board" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "sprint": {
    "id": "...",
    "name": "...",
    "status": "..."
  },
  "columns": [
    {
      "status": "...",
      "tickets": []
    }
  ]
}

CLI shortcut: alloy sprint get $SPRINT_ID

6. Start the Sprint

When the team is ready and tickets are assigned, start the sprint:

curl -s -X POST "$BASE_URL/api/v1/sprints/$SPRINT_ID/start" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 4 — Payments Integration",
  "goal": "Ship Stripe checkout and invoice generation",
  "start_date": "2026-04-01",
  "end_date": "2026-04-15",
  "status": "Active",
  "created_at": "...",
  "updated_at": "..."
}

Only one sprint per project should be active at a time. Starting a sprint signals to the team that work begins now.

CLI shortcut: alloy sprint start $SPRINT_ID

7. Run Daily Standups

Check the sprint board each morning to see where tickets are:

curl -s "$BASE_URL/api/v1/sprints/$SPRINT_ID/board" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "sprint": {
    "id": "...",
    "name": "...",
    "status": "..."
  },
  "columns": [
    {
      "status": "...",
      "tickets": []
    }
  ]
}

Move tickets between columns by transitioning their status (see Sprints & Boards for transition details). If tickets are stuck, add a comment to flag the issue:

curl -s -X POST "$BASE_URL/api/v1/tickets/$TICKET_ID/comments" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"body\": \"Blocked on API credentials from vendor. ETA?\", \"author_id\": \"$USER_ID\"}" | jq .
{
  "id": "...",
  "ticket_id": "...",
  "author_id": "...",
  "body": "Blocked on API credentials from vendor. ETA?",
  "created_at": "..."
}

CLI shortcut: alloy comment add $TICKET_ID --body "Blocked on API credentials"

8. Track Progress with Burndown

Check the burndown daily to spot trends early:

curl -s "$BASE_URL/api/v1/sprints/$SPRINT_ID/burndown" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "sprint_id": "...",
  "data": [
    {
      "date": "...",
      "total_tickets": 0,
      "completed_tickets": 0,
      "remaining_tickets": 0
    }
  ]
}

What to look for as a PM:

PatternSignalAction
Flat remaining lineNo tickets finishingCheck blockers
Rising remainingScope creepCut scope or extend
Steep early dropTeam crushing itPlan for next sprint
Cliff at endLast-minute rushImprove estimation

A healthy burndown trends downward steadily. If it flatlines for more than two days mid-sprint, intervene.

9. Mid-Sprint Adjustments

Sometimes scope changes. Update the sprint goal if the direction shifts:

curl -s -X PATCH "$BASE_URL/api/v1/sprints/$SPRINT_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"goal": "Ship Stripe checkout (invoicing moved to Sprint 5)"}' | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 4 — Payments Integration",
  "goal": "Ship Stripe checkout (invoicing moved to Sprint 5)",
  "start_date": "...",
  "end_date": "...",
  "status": "Active",
  "created_at": "...",
  "updated_at": "..."
}

Document why you cut scope — your future self will thank you during retro.

CLI shortcut: alloy sprint update $SPRINT_ID --goal "Updated goal"

10. Close the Sprint

When the iteration ends, complete the sprint:

curl -s -X POST "$BASE_URL/api/v1/sprints/$SPRINT_ID/complete" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 4 — Payments Integration",
  "goal": "...",
  "start_date": "...",
  "end_date": "...",
  "status": "Completed",
  "created_at": "...",
  "updated_at": "..."
}

Unfinished tickets stay in their current status. Move them to the next sprint or back to the backlog.

CLI shortcut: alloy sprint complete $SPRINT_ID

11. Run a Retrospective

Use ticket activity and time data to fuel your retro. Pull comments for a ticket to review discussion threads:

curl -s "$BASE_URL/api/v1/tickets/$TICKET_ID/comments" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": [
    {
      "id": "...",
      "ticket_id": "...",
      "author_id": "...",
      "body": "...",
      "created_at": "..."
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Key retro questions for PMs:

  • Did we hit the sprint goal?
  • Which tickets carried over, and why?
  • Were estimates accurate? Where did we over/underestimate?
  • Were there blockers we could have anticipated?
  • What should we do differently next sprint?

12. Plan the Next Sprint

Create the next sprint and assign carryover tickets. List all sprints to see what has been completed:

curl -s "$BASE_URL/api/v1/projects/$PROJECT_ID/sprints" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": [
    {
      "id": "...",
      "project_id": "...",
      "name": "...",
      "status": "..."
    }
  ],
  "next_cursor": null,
  "has_more": false
}

Create the next sprint:

curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/sprints" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Sprint 5 — Invoicing & Notifications",
    "goal": "Ship invoice generation and email notifications",
    "start_date": "2026-04-16",
    "end_date": "2026-04-30"
  }' | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 5 — Invoicing & Notifications",
  "goal": "Ship invoice generation and email notifications",
  "start_date": "2026-04-16",
  "end_date": "2026-04-30",
  "status": "Planned",
  "created_at": "...",
  "updated_at": "..."
}

Each sprint should improve on the last. Use retro insights to adjust sprint length, team capacity, and scope.

CLI shortcut: alloy sprint create --project $PROJECT_ID --name "Sprint 5" --goal "Ship invoicing" --start-date 2026-04-16 --end-date 2026-04-30