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:
| Role | Who gets it |
|---|---|
| Admin | Tech leads who manage workflows and invites |
| Member | Engineers who create tickets, log time, run sprints |
| Reporter | Contractors or PMs who file tickets but shouldn’t manage projects |
| Viewer | Stakeholders 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:
| Step | Done? |
|---|---|
| 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:
- Set up daily standups using the sprint board — see Running a Sprint (for PMs)
- Configure time tracking for your team — see Time Tracking & Finance
- Review the For Engineering Managers guide for ongoing operations like reporting, workflow enforcement, and standup automation