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 — Setting Up a New Team (for EMs)

This playbook walks an engineering manager through setting up a brand-new team in Alloy, from creating the organization to launching the first sprint. Every step uses the API directly — you can script any of it, wire it into Slack, or call it through the MCP server.

For role and permission details, see Teams, Roles & Permissions. For sprint operations, see Sprints & Boards.

1. Prerequisites and Setup

Set shell variables so the examples are copy-pasteable:

BASE_URL="http://localhost:3000"

Register your account and capture the token:

curl -s -X POST "$BASE_URL/api/v1/auth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "em-setup@alloy.dev",
    "password": "em-setup-pass1",
    "display_name": "Team Setup EM"
  }' | jq .
{
  "user_id": "...",
  "email": "em-setup@alloy.dev",
  "display_name": "Team Setup EM",
  "access_token": "..."
}

Save the token and user ID:

TOKEN="<access_token from above>"
USER_ID="<user_id from above>"

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": "..."
}

2. Create the Organization

Every team lives inside an organization. Create one with a unique slug:

curl -s -X POST "$BASE_URL/api/v1/orgs" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Platform Engineering",
    "slug": "platform-eng-setup"
  }' | jq .
{
  "id": "...",
  "name": "Platform Engineering",
  "slug": "platform-eng-setup"
}

Save the org ID:

ORG_ID="<id from above>"

You are automatically the Owner of any organization you create. This gives you full control over membership, workflows, and settings.

3. Verify Your Identity and Role

Confirm your auth is working and you have Owner role:

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

CLI shortcut: alloy auth me

4. Define a Workflow

Before creating projects, define how tickets flow through your process. A workflow specifies statuses and allowed transitions. Set enforcement to strict so the system rejects invalid status changes:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workflows" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Platform Team Flow",
    "statuses": [
      {"name": "Backlog", "category": "todo"},
      {"name": "Ready", "category": "todo"},
      {"name": "InProgress", "category": "in_progress"},
      {"name": "InReview", "category": "in_progress"},
      {"name": "Done", "category": "done"}
    ],
    "transitions": [
      {"from": "Backlog", "to": "Ready"},
      {"from": "Ready", "to": "InProgress"},
      {"from": "InProgress", "to": "InReview"},
      {"from": "InReview", "to": "Done"},
      {"from": "InReview", "to": "InProgress"}
    ],
    "enforcement": "strict"
  }' | jq .
{
  "id": "...",
  "org_id": "...",
  "name": "Platform Team Flow",
  "statuses": [
    {"name": "Backlog", "category": "todo"},
    {"name": "Ready", "category": "todo"},
    {"name": "InProgress", "category": "in_progress"},
    {"name": "InReview", "category": "in_progress"},
    {"name": "Done", "category": "done"}
  ],
  "transitions": [
    {"from": "Backlog", "to": "Ready"},
    {"from": "Ready", "to": "InProgress"},
    {"from": "InProgress", "to": "InReview"},
    {"from": "InReview", "to": "Done"},
    {"from": "InReview", "to": "InProgress"}
  ],
  "enforcement": "strict",
  "created_at": "...",
  "updated_at": "..."
}

Save the workflow ID:

WORKFLOW_ID="<id from above>"

With strict enforcement, developers cannot skip steps — a ticket must go through Ready before it can move to InProgress. This prevents the “everything jumps to Done” problem.

CLI shortcut: alloy workflow create --org $ORG_ID --name "Platform Team Flow"

5. Create the First Project

Create a project for the team’s work:

curl -s -X POST "$BASE_URL/api/v1/projects" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"org_id\": \"$ORG_ID\",
    \"key\": \"PLAT\",
    \"name\": \"Platform Services\",
    \"description\": \"Core platform infrastructure and developer tools\"
  }" | jq .
{
  "id": "...",
  "org_id": "...",
  "team_id": null,
  "workflow_id": null,
  "key": "PLAT",
  "name": "Platform Services",
  "description": "Core platform infrastructure and developer tools",
  "ticket_counter": 0,
  "capitalization_type": null,
  "development_phase": null,
  "cost_center_id": null,
  "amortization_months": null,
  "budget_cents": null,
  "budget_period": null,
  "created_at": "...",
  "updated_at": "..."
}

Save the project ID:

PROJECT_ID="<id from above>"

CLI shortcut: alloy project create --org $ORG_ID --key PLAT --name "Platform Services"

6. Set a Budget

If your team has a quarterly budget, set it on the project:

curl -s -X PATCH "$BASE_URL/api/v1/projects/$PROJECT_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"budget_cents": 50000000, "budget_period": "Quarterly"}' | jq .
{
  "id": "...",
  "org_id": "...",
  "key": "PLAT",
  "name": "Platform Services",
  "description": "Core platform infrastructure and developer tools",
  "ticket_counter": 0,
  "budget_cents": 50000000,
  "budget_period": "Quarterly",
  "created_at": "...",
  "updated_at": "..."
}

This sets a $500,000 quarterly budget. Finance teams can use this for capitalization and budget tracking reports — see Time Tracking & Finance for details.

7. Create Labels

Labels help categorize tickets across the organization. Set up your standard taxonomy:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/labels" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "bug", "color": "#FF0000"}' | jq .
{
  "id": "...",
  "org_id": "...",
  "name": "bug",
  "color": "#FF0000",
  "created_at": "...",
  "updated_at": "..."
}

Create a few more labels for common categories:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/labels" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "feature", "color": "#00AA00"}' | jq .
{
  "id": "...",
  "org_id": "...",
  "name": "feature",
  "color": "#00AA00",
  "created_at": "...",
  "updated_at": "..."
}

A typical team needs 5-8 labels: bug, feature, tech-debt, docs, security, blocked. Avoid label sprawl — too many defeats the purpose.

For more on labels and tags, see Labels, Tags & Organization.

8. Invite Team Members

Send invites to your team. Each invite generates a unique link:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/invites" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"email\": \"dev1-setup@alloy.dev\", \"role\": \"member\", \"created_by\": \"$USER_ID\"}" | jq .
{
  "id": "...",
  "invite_code": "...",
  "invite_link": "...",
  "email": "dev1-setup@alloy.dev",
  "role": "Member",
  "expires_at": "..."
}

Send an admin invite for your tech lead:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/invites" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"email\": \"lead-setup@alloy.dev\", \"role\": \"admin\", \"created_by\": \"$USER_ID\"}" | jq .
{
  "id": "...",
  "invite_code": "...",
  "invite_link": "...",
  "email": "lead-setup@alloy.dev",
  "role": "Admin",
  "expires_at": "..."
}

Share the invite link with each person. They register through the link and automatically join the org with the assigned role.

Role guide for EMs:

RoleWho gets it
AdminTech leads who manage workflows and invites
MemberEngineers who create tickets, log time, run sprints
ReporterContractors or PMs who file tickets but shouldn’t manage projects
ViewerStakeholders who need read-only access

9. Verify Organization Members

After people accept invites, check who has joined:

curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/members" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": [
    {
      "user_id": "...",
      "display_name": "...",
      "email": "...",
      "role": "...",
      "joined_at": "..."
    }
  ]
}

CLI shortcut: alloy org members $ORG_ID

10. Add Members to the Project

Assign team members to the project. This matters for Reporters — they can only see projects they are members of:

curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/members" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"user_id\": \"$USER_ID\"}" | jq .
{
  "project_id": "...",
  "user_id": "...",
  "created_at": "..."
}

List project members to confirm:

curl -s "$BASE_URL/api/v1/projects/$PROJECT_ID/members" \
  -H "Authorization: Bearer $TOKEN" | jq .
[
  {
    "project_id": "...",
    "user_id": "...",
    "created_at": "..."
  }
]

Add every team member to the project. Members and above can see all projects regardless of membership, but explicit membership keeps things organized and enables Reporter-scoped access.

11. Create Seed Tickets

Populate the backlog with initial work items so the team has something to start with:

curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"title\": \"Set up CI/CD pipeline\",
    \"description\": \"Configure GitHub Actions for build, test, and deploy\",
    \"priority\": \"High\",
    \"reporter_id\": \"$USER_ID\"
  }" | jq .
{
  "id": "...",
  "project_id": "...",
  "ticket_number": 1,
  "title": "Set up CI/CD pipeline",
  "description": "Configure GitHub Actions for build, test, and deploy",
  "status": "Backlog",
  "priority": "High",
  "assignee_id": null,
  "reporter_id": "...",
  "sprint_id": null,
  "created_at": "...",
  "updated_at": "..."
}

Create a few more tickets:

curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{
    \"title\": \"Document onboarding process\",
    \"description\": \"Write a getting-started guide for new team members\",
    \"priority\": \"Medium\",
    \"reporter_id\": \"$USER_ID\"
  }" | jq .
{
  "id": "...",
  "project_id": "...",
  "ticket_number": 2,
  "title": "Document onboarding process",
  "description": "Write a getting-started guide for new team members",
  "status": "Backlog",
  "priority": "Medium",
  "assignee_id": null,
  "reporter_id": "...",
  "sprint_id": null,
  "created_at": "...",
  "updated_at": "..."
}

Aim for 10-15 backlog items before the first sprint. Include a mix of quick wins and larger initiatives so the team can build momentum.

12. Assign Tickets

Assign owners to tickets before the sprint begins:

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": "..."
}

Every ticket should have an owner. Unowned tickets drift — as an EM, make it a rule that nothing enters a sprint without an assignee.

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

13. Create the First Sprint

Set up a two-week sprint with a focused 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 1 — Foundation",
    "goal": "Set up CI/CD, dev environment, and onboarding docs",
    "start_date": "2026-04-01",
    "end_date": "2026-04-15"
  }' | jq .
{
  "id": "...",
  "project_id": "...",
  "name": "Sprint 1 — Foundation",
  "goal": "Set up CI/CD, dev environment, and onboarding docs",
  "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>"

For a new team, keep the first sprint light. The goal is to establish rhythm, not to ship the entire roadmap.

CLI shortcut: alloy sprint create --project $PROJECT_ID --name "Sprint 1" --goal "Foundation setup"

14. Set Up Webhooks

Wire Alloy events into your team’s communication channels. Create a webhook for ticket updates:

curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/webhooks" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "url": "https://hooks.slack.example.com/alloy-platform",
    "event_types": ["ticket.created", "ticket.status_changed", "sprint.started", "sprint.completed"]
  }' | jq .
{
  "id": "...",
  "org_id": "...",
  "url": "...",
  "secret": "...",
  "event_types": [
    "ticket.created",
    "ticket.status_changed",
    "sprint.started",
    "sprint.completed"
  ],
  "active": true,
  "created_at": "...",
  "updated_at": "..."
}

Store the secret securely — it is only returned on creation and is used to verify webhook signatures via the X-Alloy-Signature header.

For more on webhooks and event-driven automation, see For DevOps & Platform Teams.

15. Create API Keys for Automation

Generate API keys for CI/CD and scripts:

curl -s -X POST "$BASE_URL/api/v1/api-keys" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "Platform CI", "scopes": ["read", "write"]}' | jq .
{
  "id": "...",
  "name": "Platform CI",
  "key": "...",
  "key_prefix": "...",
  "scopes": ["read", "write"],
  "project_ids": [],
  "created_at": "...",
  "expires_at": null
}

Store the key value in your CI secrets. The full key is only shown once. Use alloy_live_ prefixed keys for production and alloy_test_ for testing environments.

Create a read-only key for dashboards:

curl -s -X POST "$BASE_URL/api/v1/api-keys" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "Dashboard Read-Only", "scopes": ["read"]}' | jq .
{
  "id": "...",
  "name": "Dashboard Read-Only",
  "key": "...",
  "key_prefix": "...",
  "scopes": ["read"],
  "project_ids": [],
  "created_at": "...",
  "expires_at": null
}

16. Verify Permissions

Before handing off to the team, verify that permissions are working correctly. List organization members to confirm roles:

curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/members" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": [
    {
      "user_id": "...",
      "display_name": "...",
      "email": "...",
      "role": "...",
      "joined_at": "..."
    }
  ]
}

Verify your team can list the project:

curl -s "$BASE_URL/api/v1/projects?org_id=$ORG_ID" \
  -H "Authorization: Bearer $TOKEN" | jq .
{
  "items": [
    {
      "id": "...",
      "org_id": "...",
      "key": "PLAT",
      "name": "Platform Services",
      "description": "Core platform infrastructure and developer tools",
      "ticket_counter": "...",
      "created_at": "...",
      "updated_at": "..."
    }
  ],
  "next_cursor": null,
  "has_more": false
}

For a complete permission testing guide, see Teams, Roles & Permissions.

17. Start the Sprint

When the team is ready, 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 1 — Foundation",
  "goal": "Set up CI/CD, dev environment, and onboarding docs",
  "start_date": "2026-04-01",
  "end_date": "2026-04-15",
  "status": "Active",
  "created_at": "...",
  "updated_at": "..."
}

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

CLI shortcut: alloy sprint start $SPRINT_ID

18. Day-One Checklist

Use this checklist to confirm everything is in place before handing off to your team:

StepDone?
Organization created with unique slug
Workflow defined with strict enforcement
Project created with key and description
Budget set (if applicable)
Labels created for standard taxonomy
All team members invited with correct roles
Team members added to project
Backlog seeded with 10-15 tickets
Tickets assigned to owners
First sprint created with focused goal
Webhooks configured for Slack/CI notifications
API keys generated for automation
Permissions verified
Sprint started

What to do next: