Playbook — Automating Your Workflow (for DevOps)
This playbook provides seven ready-to-use automation recipes for DevOps and platform engineers. Each recipe is a complete, copy-pasteable shell script that solves a real operational problem using the Alloy API.
For scripting fundamentals (capturing IDs, chaining requests, pagination), see the API Automation Tutorial. For deployment and API key setup, see For DevOps & Platform Teams.
Prerequisites
All recipes assume these environment variables are set:
BASE_URL="http://localhost:3000"
Register a user and capture credentials:
curl -s -X POST "$BASE_URL/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{
"email": "devops-auto@alloy.dev",
"password": "devops-auto-pass1",
"display_name": "DevOps Automation"
}' | jq .
{
"user_id": "...",
"email": "devops-auto@alloy.dev",
"display_name": "DevOps Automation",
"access_token": "..."
}
Save the token:
TOKEN="<access_token from above>"
USER_ID="<user_id from above>"
Create an organization for automation:
curl -s -X POST "$BASE_URL/api/v1/orgs" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "DevOps Automation Org",
"slug": "devops-auto-org"
}' | jq .
{
"id": "...",
"name": "DevOps Automation Org",
"slug": "devops-auto-org"
}
Create a project:
curl -s -X POST "$BASE_URL/api/v1/projects" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"$ORG_ID\",
\"key\": \"INFRA\",
\"name\": \"Infra Platform\"
}" | jq .
{
"id": "...",
"org_id": "...",
"key": "INFRA",
"name": "Infra Platform",
"ticket_counter": 0
}
Recipe 1: Health Check Monitor
Monitor Alloy availability from a cron job or CI step. The /health
endpoint is unauthenticated and returns the service status and database
backend.
curl -s "$BASE_URL/health" | jq .
{
"status": "ok",
"service": "alloy",
"version": "0.1.0",
"database": "...",
"db_healthy": true,
"migration_version": "...",
"uptime_seconds": "..."
}
Full monitoring script (use plain code blocks for scripts with logic):
#!/bin/bash
# health-monitor.sh — Exit 1 if Alloy is unhealthy
set -euo pipefail
ALLOY_URL="${ALLOY_URL:-http://localhost:3000}"
STATUS=$(curl -sf "$ALLOY_URL/health" | jq -r '.status')
if [ "$STATUS" != "ok" ]; then
echo "ALERT: Alloy health check failed — status=$STATUS"
exit 1
fi
echo "OK: Alloy healthy"
Wire this into cron (*/5 * * * *), Kubernetes liveness probes, or your
uptime monitor.
Recipe 2: Automated Ticket Creation from CI
Create a ticket automatically when a CI pipeline detects an issue — for example, a failed deployment or a flaky test suite.
Create a ticket in the project:
curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"title": "CI: Deploy failed on staging",
"description": "Automated ticket from CI pipeline. Build #1234 failed during database migration step.",
"priority": "High",
"reporter_id": "'$USER_ID'"
}' | jq .
{
"id": "...",
"project_id": "...",
"title": "CI: Deploy failed on staging",
"description": "Automated ticket from CI pipeline. Build #1234 failed during database migration step.",
"priority": "High",
"status": "..."
}
Add a comment with build details:
curl -s -X POST "$BASE_URL/api/v1/tickets/$TICKET_ID/comments" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"body": "Build log: deployment failed at migration step V42. Rollback initiated.",
"author_id": "'$USER_ID'"
}' | jq .
{
"id": "...",
"ticket_id": "...",
"body": "Build log: deployment failed at migration step V42. Rollback initiated.",
"author_id": "..."
}
Full CI script:
#!/bin/bash
# create-incident-ticket.sh — Create a ticket when deploy fails
set -euo pipefail
ALLOY_URL="${ALLOY_URL:-http://localhost:3000}"
ALLOY_TOKEN="${ALLOY_TOKEN}"
ALLOY_PROJECT_ID="${ALLOY_PROJECT_ID}"
ALLOY_USER_ID="${ALLOY_USER_ID}"
BUILD_NUMBER="${BUILD_NUMBER:-unknown}"
FAILURE_REASON="${FAILURE_REASON:-unspecified}"
TICKET_ID=$(curl -sf -X POST "$ALLOY_URL/api/v1/projects/$ALLOY_PROJECT_ID/tickets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
-d "{
\"title\": \"CI: Build #$BUILD_NUMBER failed\",
\"description\": \"Automated incident ticket from CI.\",
\"priority\": \"High\",
\"reporter_id\": \"$ALLOY_USER_ID\"
}" | jq -r '.id')
curl -sf -X POST "$ALLOY_URL/api/v1/tickets/$TICKET_ID/comments" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
-d "{
\"body\": \"Failure reason: $FAILURE_REASON\",
\"author_id\": \"$ALLOY_USER_ID\"
}" > /dev/null
echo "Created incident ticket: $TICKET_ID"
Recipe 3: API Key Rotation
Rotate API keys periodically. Create a new key, update your secrets manager, then revoke the old one.
Create a new API key:
curl -s -X POST "$BASE_URL/api/v1/api-keys" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Rotated CI Key",
"scopes": ["read", "write"]
}' | jq .
{
"id": "...",
"name": "Rotated CI Key",
"key": "...",
"key_prefix": "...",
"scopes": ["read", "write"],
"project_ids": [],
"created_at": "...",
"expires_at": null
}
List existing keys to find old ones:
curl -s "$BASE_URL/api/v1/api-keys" \
-H "Authorization: Bearer $TOKEN" | jq .
[
{
"id": "...",
"name": "Rotated CI Key",
"key_prefix": "...",
"scopes": ["read", "write"],
"project_ids": [],
"created_at": "...",
"last_used_at": null,
"expires_at": null
}
]
Revoke the old key by deleting it (returns 204 No Content):
curl -s -X DELETE "$BASE_URL/api/v1/api-keys/$API_KEY_ID" \
-H "Authorization: Bearer $TOKEN" -w "%{http_code}" -o /dev/null
Full rotation script:
#!/bin/bash
# rotate-api-key.sh — Create new key, output it, revoke old key
set -euo pipefail
ALLOY_URL="${ALLOY_URL:-http://localhost:3000}"
ALLOY_TOKEN="${ALLOY_TOKEN}"
OLD_KEY_ID="${1:?Usage: rotate-api-key.sh <old-key-id>}"
# Create new key
NEW_KEY_JSON=$(curl -sf -X POST "$ALLOY_URL/api/v1/api-keys" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
-d '{"name": "Rotated CI Key", "scopes": ["read", "write"]}')
NEW_KEY=$(echo "$NEW_KEY_JSON" | jq -r '.key')
echo "New key created: $(echo "$NEW_KEY_JSON" | jq -r '.key_prefix')..."
# Store the new key in your secrets manager here
# e.g., vault kv put secret/alloy-api-key value="$NEW_KEY"
# Revoke old key
curl -sf -X DELETE "$ALLOY_URL/api/v1/api-keys/$OLD_KEY_ID" \
-H "Authorization: Bearer $ALLOY_TOKEN"
echo "Old key $OLD_KEY_ID revoked"
Recipe 4: Webhook Setup for Event-Driven Pipelines
Set up webhooks to trigger external systems when tickets change. Use this to drive CI/CD pipelines, Slack notifications, or audit logs.
Create a webhook for ticket events:
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.example.com/alloy-auto",
"event_types": ["ticket.created", "ticket.updated", "ticket.status_changed"]
}' | jq .
{
"id": "...",
"org_id": "...",
"url": "https://hooks.example.com/alloy-auto",
"secret": "...",
"event_types": ["ticket.created", "ticket.updated", "ticket.status_changed"],
"active": true,
"created_at": "...",
"updated_at": "..."
}
Save the secret — it is only shown at creation time. Use it to verify
webhook signatures via the X-Alloy-Signature header (HMAC-SHA256).
List webhooks to confirm:
curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/webhooks" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"org_id": "...",
"url": "https://hooks.example.com/alloy-auto",
"event_types": ["ticket.created", "ticket.updated", "ticket.status_changed"],
"active": true,
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null
}
Check delivery status:
curl -s "$BASE_URL/api/v1/webhooks/$WEBHOOK_ID/deliveries" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [],
"next_cursor": null
}
Recipe 5: Sprint Automation
Automate sprint lifecycle — create a sprint, add tickets, start it, and close it when the iteration is done.
Create a sprint:
curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/sprints" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Auto Sprint 1",
"start_date": "2026-04-01",
"end_date": "2026-04-15"
}' | jq .
{
"id": "...",
"project_id": "...",
"name": "Auto Sprint 1",
"start_date": "2026-04-01",
"end_date": "2026-04-15",
"status": "Planned"
}
List tickets in the project to find one for the sprint:
curl -s "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets?limit=5" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"project_id": "...",
"title": "...",
"status": "..."
}
],
"next_cursor": null,
"has_more": false
}
Start the sprint:
curl -s -X POST "$BASE_URL/api/v1/sprints/$SPRINT_ID/start" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"id": "...",
"name": "Auto Sprint 1",
"status": "Active"
}
Full sprint lifecycle script:
#!/bin/bash
# sprint-lifecycle.sh — Create, populate, and start a sprint
set -euo pipefail
ALLOY_URL="${ALLOY_URL:-http://localhost:3000}"
ALLOY_TOKEN="${ALLOY_TOKEN}"
PROJECT_ID="${ALLOY_PROJECT_ID}"
START_DATE=$(date -u +%Y-%m-%d)
END_DATE=$(date -u -v+14d +%Y-%m-%d 2>/dev/null || date -u -d "+14 days" +%Y-%m-%d)
SPRINT_ID=$(curl -sf -X POST "$ALLOY_URL/api/v1/projects/$PROJECT_ID/sprints" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
-d "{\"name\": \"Sprint $(date +%V)\", \"start_date\": \"$START_DATE\", \"end_date\": \"$END_DATE\"}" \
| jq -r '.id')
echo "Created sprint: $SPRINT_ID"
# Assign unassigned tickets to the sprint
TICKET_IDS=$(curl -sf "$ALLOY_URL/api/v1/projects/$PROJECT_ID/tickets?limit=50" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
| jq -r '.items[] | select(.sprint_id == null) | .id')
for TID in $TICKET_IDS; do
curl -sf -X PATCH "$ALLOY_URL/api/v1/tickets/$TID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
-d "{\"sprint_id\": \"$SPRINT_ID\"}" > /dev/null
echo " Assigned ticket $TID"
done
# Start the sprint
curl -sf -X POST "$ALLOY_URL/api/v1/sprints/$SPRINT_ID/start" \
-H "Authorization: Bearer $ALLOY_TOKEN" > /dev/null
echo "Sprint started"
Recipe 6: Bulk Ticket Updates
Update multiple tickets at once — for example, re-prioritize all open tickets or move them to a new status when plans change.
List tickets in the project:
curl -s "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets?limit=20" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"project_id": "...",
"ticket_number": "...",
"title": "...",
"status": "...",
"priority": "..."
}
],
"next_cursor": null,
"has_more": false
}
Update a ticket’s priority:
curl -s -X PATCH "$BASE_URL/api/v1/tickets/$TICKET_ID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"priority": "Urgent"
}' | jq .
{
"id": "...",
"title": "...",
"status": "...",
"priority": "Urgent"
}
Full bulk update script:
#!/bin/bash
# bulk-reprioritize.sh — Set all open tickets to a given priority
set -euo pipefail
ALLOY_URL="${ALLOY_URL:-http://localhost:3000}"
ALLOY_TOKEN="${ALLOY_TOKEN}"
PROJECT_ID="${ALLOY_PROJECT_ID}"
NEW_PRIORITY="${1:-Medium}"
TICKET_IDS=$(curl -sf "$ALLOY_URL/api/v1/projects/$PROJECT_ID/tickets?limit=100" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
| jq -r '.items[] | select(.status != "Done") | .id')
COUNT=0
for TID in $TICKET_IDS; do
curl -sf -X PATCH "$ALLOY_URL/api/v1/tickets/$TID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ALLOY_TOKEN" \
-d "{\"priority\": \"$NEW_PRIORITY\"}" > /dev/null
COUNT=$((COUNT + 1))
done
echo "Updated $COUNT tickets to priority=$NEW_PRIORITY"
Recipe 7: Scheduled Reporting
Generate reports on a schedule for stakeholders — capitalization data, sprint burndowns, or time tracking summaries.
Create a label for tracking automation:
curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/labels" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "automated-report", "color": "#6366f1"}' | jq .
{
"id": "...",
"name": "automated-report",
"color": "#6366f1"
}
List projects to get current status:
curl -s "$BASE_URL/api/v1/projects?org_id=$ORG_ID" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"name": "...",
"org_id": "..."
}
],
"next_cursor": null,
"has_more": false
}
Full reporting script:
#!/bin/bash
# weekly-report.sh — Generate a weekly project status report
set -euo pipefail
ALLOY_URL="${ALLOY_URL:-http://localhost:3000}"
ALLOY_TOKEN="${ALLOY_TOKEN}"
PROJECT_ID="${ALLOY_PROJECT_ID}"
REPORT_FILE="alloy-report-$(date +%Y%m%d).json"
echo '{"generated_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",' > "$REPORT_FILE"
# Project info
echo '"project":' >> "$REPORT_FILE"
curl -sf "$ALLOY_URL/api/v1/projects/$PROJECT_ID" \
-H "Authorization: Bearer $ALLOY_TOKEN" >> "$REPORT_FILE"
echo ',' >> "$REPORT_FILE"
# Open tickets
echo '"tickets":' >> "$REPORT_FILE"
curl -sf "$ALLOY_URL/api/v1/projects/$PROJECT_ID/tickets?limit=100" \
-H "Authorization: Bearer $ALLOY_TOKEN" >> "$REPORT_FILE"
echo ',' >> "$REPORT_FILE"
# Active sprints
echo '"sprints":' >> "$REPORT_FILE"
curl -sf "$ALLOY_URL/api/v1/projects/$PROJECT_ID/sprints" \
-H "Authorization: Bearer $ALLOY_TOKEN" >> "$REPORT_FILE"
echo '}' >> "$REPORT_FILE"
echo "Report saved to $REPORT_FILE"
echo "Tickets: $(jq '.tickets.items | length' "$REPORT_FILE")"
Next Steps
- API Automation Tutorial — scripting patterns, pagination, and error handling
- For DevOps & Platform Teams — deployment, API keys, webhooks, and GitHub integration
- API Reference — complete endpoint documentation
- MCP Tools Reference — automate via MCP instead of curl