Time Tracking & Finance
Alloy includes built-in time tracking, approval workflows, labor-rate management, and capitalization reporting. Engineers log time against tickets, managers approve entries, and finance teams generate ASC 350-40 / IAS 38 compliant reports — all through the same API. This guide covers the full lifecycle from logging your first hour to exporting a capitalization CSV.
1. Prerequisites
You need a running Alloy server and a registered user. Set these shell variables for the examples below:
BASE_URL="http://localhost:3000"
Register a user and capture the token:
curl -s -X POST "$BASE_URL/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d '{
"email": "guide-tf@alloy.dev",
"password": "guide-tf-pass1",
"display_name": "TF Guide User"
}' | jq .
{
"user_id": "...",
"email": "guide-tf@alloy.dev",
"display_name": "TF Guide User",
"access_token": "..."
}
Save the token and user ID:
TOKEN="<access_token from above>"
USER_ID="<user_id from above>"
Create an organization:
curl -s -X POST "$BASE_URL/api/v1/orgs" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Finance Org",
"slug": "guide-tf-org"
}' | jq .
{
"id": "...",
"name": "Finance Org",
"slug": "guide-tf-org"
}
ORG_ID="<id from above>"
Create a project with finance fields:
curl -s -X POST "$BASE_URL/api/v1/projects" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"org_id\": \"$ORG_ID\",
\"key\": \"FIN\",
\"name\": \"Finance Feature\",
\"description\": \"Project for time tracking guide\",
\"capitalization_type\": \"Capex\",
\"development_phase\": \"AppDevelopment\",
\"cost_center_id\": \"ENG-001\",
\"budget_cents\": 5000000,
\"budget_period\": \"Quarterly\",
\"amortization_months\": 36
}" | jq .
{
"id": "...",
"org_id": "...",
"key": "FIN",
"name": "Finance Feature",
"description": "Project for time tracking guide",
"ticket_counter": 0,
"capitalization_type": "Capex",
"development_phase": "AppDevelopment",
"cost_center_id": "ENG-001",
"budget_cents": 5000000,
"budget_period": "Quarterly",
"amortization_months": 36
}
PROJECT_ID="<id from above>"
Create a ticket to log time against:
curl -s -X POST "$BASE_URL/api/v1/projects/$PROJECT_ID/tickets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"title\": \"Implement SSO login\",
\"description\": \"Add SAML-based single sign-on\",
\"reporter_id\": \"$USER_ID\"
}" | jq .
{
"id": "...",
"project_id": "...",
"ticket_number": 1,
"title": "Implement SSO login",
"description": "Add SAML-based single sign-on",
"status": "...",
"assignee_id": null,
"reporter_id": "...",
"created_at": "...",
"updated_at": "..."
}
TICKET_ID="<id from above>"
2. Understanding Activity Types
Every time entry requires an activity_type that classifies the work
performed. Alloy defines 13 activity types used for reporting and
capitalization accounting:
| Activity Type | Description | CapEx Eligible |
|---|---|---|
Coding | Writing or modifying application code | Yes |
Testing | Writing or running tests | Yes |
CodeReview | Reviewing pull requests and code | Yes |
Design | UI/UX design work | Yes |
Architecture | System design and architecture decisions | Yes |
PM | Project management tasks | Depends on phase |
Requirements | Gathering and documenting requirements | Planning phase only |
Training | Learning, onboarding, or training activities | No |
Maintenance | Infrastructure and dependency maintenance | No (OpEx) |
BugFixing | Investigating and fixing bugs | Yes (Development phase) |
Documentation | Writing or updating documentation | Yes |
Deployment | Deploying, releasing, or CI/CD work | Yes |
Meetings | Meetings, standups, and ceremonies | Depends on context |
Activity types map to capitalization categories in reports. Work classified
as Coding, Testing, or Design during a Development phase project
is typically capitalizable under ASC 350-40.
3. Creating Time Entries
Log time against a ticket with a specific activity type and duration:
curl -s -X POST "$BASE_URL/api/v1/time-entries" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"user_id\": \"$USER_ID\",
\"ticket_id\": \"$TICKET_ID\",
\"project_id\": \"$PROJECT_ID\",
\"date\": \"2026-03-28\",
\"duration_minutes\": 120,
\"description\": \"Implemented SAML flow\",
\"activity_type\": \"Coding\"
}" | jq .
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 120,
"description": "Implemented SAML flow",
"activity_type": "Coding",
"status": "Draft",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
TIME_ENTRY_ID="<id from above>"
New entries always start in Draft status. You can log additional entries
with different activity types (e.g. CodeReview, Testing) against the
same ticket.
4. Viewing Time Entries
Retrieve a single time entry by ID:
curl -s "$BASE_URL/api/v1/time-entries/$TIME_ENTRY_ID" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 120,
"description": "Implemented SAML flow",
"activity_type": "Coding",
"status": "Draft",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
List all time entries for a ticket:
curl -s "$BASE_URL/api/v1/tickets/$TICKET_ID/time-entries" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 120,
"description": "Implemented SAML flow",
"activity_type": "Coding",
"status": "Draft",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null,
"has_more": false
}
List all time entries for a user:
curl -s "$BASE_URL/api/v1/users/$USER_ID/time-entries" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "...",
"duration_minutes": 120,
"description": "...",
"activity_type": "...",
"status": "...",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null,
"has_more": false
}
Both list endpoints support cursor and limit query parameters for
pagination, plus optional project_id and date filters.
5. Editing Time Entries
Update a draft time entry before submitting it. You can change the date, duration, description, or activity type:
curl -s -X PATCH "$BASE_URL/api/v1/time-entries/$TIME_ENTRY_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"duration_minutes": 150,
"description": "Implemented SAML flow and wrote integration tests"
}' | jq .
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 150,
"description": "Implemented SAML flow and wrote integration tests",
"activity_type": "Coding",
"status": "Draft",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
To delete a time entry entirely:
curl -s -X DELETE "$BASE_URL/api/v1/time-entries/$TIME_ENTRY_ID" \
-H "Authorization: Bearer $TOKEN"
Returns 204 No Content on success.
6. Submitting for Approval
When your time entries are ready, submit them for manager review. This
transitions the entry from Draft to Submitted:
curl -s -X POST "$BASE_URL/api/v1/time-entries/$TIME_ENTRY_ID/submit" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 150,
"description": "Implemented SAML flow and wrote integration tests",
"activity_type": "Coding",
"status": "Submitted",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
Once submitted, the entry cannot be edited until it is either approved or rejected. The status lifecycle is:
Draft → Submitted → Approved
→ Rejected
7. Approving Time Entries
Managers and admins can list all submitted entries awaiting approval:
curl -s "$BASE_URL/api/v1/time-entries/submitted" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 150,
"description": "Implemented SAML flow and wrote integration tests",
"activity_type": "Coding",
"status": "Submitted",
"approved_by": null,
"approved_at": null,
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null,
"has_more": false
}
Approve a submitted entry (requires Admin role):
curl -s -X POST "$BASE_URL/api/v1/time-entries/$TIME_ENTRY_ID/approve" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"id": "...",
"user_id": "...",
"ticket_id": "...",
"project_id": "...",
"date": "2026-03-28",
"duration_minutes": 150,
"description": "Implemented SAML flow and wrote integration tests",
"activity_type": "Coding",
"status": "Approved",
"approved_by": "...",
"approved_at": "...",
"created_at": "...",
"updated_at": "..."
}
Only Approved time entries are included in capitalization reports.
8. Setting Labor Rates
Labor rates define the loaded cost per hour for each user, used in capitalization calculations. Rates require Admin or Owner role.
Create a labor rate:
curl -s -X POST "$BASE_URL/api/v1/labor-rates" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"user_id\": \"$USER_ID\",
\"org_id\": \"$ORG_ID\",
\"loaded_rate_cents\": 15000,
\"effective_date\": \"2026-01-01\"
}" | jq .
{
"id": "...",
"user_id": "...",
"org_id": "...",
"loaded_rate_cents": 15000,
"effective_date": "2026-01-01",
"created_at": "...",
"updated_at": "..."
}
LABOR_RATE_ID="<id from above>"
A loaded_rate_cents of 15000 means $150.00 per hour. Rates take effect
from the effective_date forward. You can set multiple rates over time to
track salary changes:
curl -s -X POST "$BASE_URL/api/v1/labor-rates" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"user_id\": \"$USER_ID\",
\"org_id\": \"$ORG_ID\",
\"loaded_rate_cents\": 16000,
\"effective_date\": \"2026-07-01\"
}" | jq .
{
"id": "...",
"user_id": "...",
"org_id": "...",
"loaded_rate_cents": 16000,
"effective_date": "2026-07-01",
"created_at": "...",
"updated_at": "..."
}
Get the current effective rate for a user:
curl -s "$BASE_URL/api/v1/users/$USER_ID/orgs/$ORG_ID/labor-rates/current" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"id": "...",
"user_id": "...",
"org_id": "...",
"loaded_rate_cents": 15000,
"effective_date": "2026-01-01",
"created_at": "...",
"updated_at": "..."
}
List all historical rates:
curl -s "$BASE_URL/api/v1/users/$USER_ID/orgs/$ORG_ID/labor-rates" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"user_id": "...",
"org_id": "...",
"loaded_rate_cents": 16000,
"effective_date": "2026-07-01",
"created_at": "...",
"updated_at": "..."
},
{
"id": "...",
"user_id": "...",
"org_id": "...",
"loaded_rate_cents": 15000,
"effective_date": "2026-01-01",
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null,
"has_more": false
}
9. Generating Capitalization Reports
Capitalization reports aggregate approved time entries with labor rates, grouped by project and activity type. They support ASC 350-40 / IAS 38 software cost accounting.
Generate a report for a specific month:
curl -s "$BASE_URL/api/v1/reports/capitalization?period=2026-03" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"period": "2026-03",
"projects": [
{
"project_id": "...",
"project_key": "FIN",
"project_name": "Finance Feature",
"capitalization_type": "Capex",
"development_phase": "AppDevelopment",
"cost_center_id": "ENG-001",
"total_hours": 2.5,
"total_amount_cents": 37500,
"breakdown": [
{
"activity_type": "Coding",
"hours": 2.5,
"amount_cents": 37500
}
]
}
]
}
Include budget tracking with include_budget=true:
curl -s "$BASE_URL/api/v1/reports/capitalization?period=2026-03&include_budget=true" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"period": "2026-03",
"projects": [
{
"project_id": "...",
"project_key": "FIN",
"project_name": "Finance Feature",
"capitalization_type": "Capex",
"development_phase": "AppDevelopment",
"cost_center_id": "ENG-001",
"total_hours": 2.5,
"total_amount_cents": 37500,
"budget_cents": 5000000,
"budget_period": "Quarterly",
"spent_cents": 37500,
"budget_remaining_cents": 4962500,
"budget_utilization_pct": 0.75,
"breakdown": [
{
"activity_type": "Coding",
"hours": 2.5,
"amount_cents": 37500
}
]
}
]
}
Group results by team or user:
curl -s "$BASE_URL/api/v1/reports/capitalization?period=2026-03&group_by=user" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"period": "2026-03",
"users": [
{
"user_id": "...",
"display_name": "TF Guide User",
"total_hours": 2.5,
"total_amount_cents": 37500,
"projects": [
{
"project_id": "...",
"project_key": "FIN",
"project_name": "Finance Feature",
"total_hours": 2.5,
"total_amount_cents": 37500,
"breakdown": [
{
"activity_type": "Coding",
"hours": 2.5,
"amount_cents": 37500
}
]
}
]
}
]
}
Filter by cost center, activity type, or tag:
curl -s "$BASE_URL/api/v1/reports/capitalization?period=2026-03&cost_center_id=ENG-001&activity_type=Coding" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"period": "2026-03",
"projects": [
{
"project_id": "...",
"project_key": "FIN",
"project_name": "Finance Feature",
"capitalization_type": "Capex",
"development_phase": "AppDevelopment",
"cost_center_id": "ENG-001",
"total_hours": 2.5,
"total_amount_cents": 37500,
"breakdown": [
{
"activity_type": "Coding",
"hours": 2.5,
"amount_cents": 37500
}
]
}
]
}
10. Exporting and Analyzing Data
Export the capitalization report as CSV for spreadsheet analysis or ERP import:
curl -s "$BASE_URL/api/v1/reports/capitalization/export?period=2026-03" \
-H "Authorization: Bearer $TOKEN" -o report.csv
The CSV includes columns:
Period,Project,ProjectKey,Employee,Department,CostCenter,Hours,ActivityType,Phase,CapExOpEx,LoadedRate,Amount,Team,Tags,BudgetCents,SpentCents,Utilization
Filter the export by tag for department-level reporting:
curl -s "$BASE_URL/api/v1/reports/capitalization/export?period=2026-03&tag=department:engineering" \
-H "Authorization: Bearer $TOKEN" -o engineering-report.csv
Tag time entries for additional reporting dimensions. First, tag a time entry:
curl -s -X PUT "$BASE_URL/api/v1/orgs/$ORG_ID/time_entry/$TIME_ENTRY_ID/tags" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tags": [
{"key": "billable", "value": "true"},
{"key": "client", "value": "acme-corp"}
]
}' | jq .
[
{
"id": "...",
"org_id": "...",
"entity_type": "time_entry",
"entity_id": "...",
"key": "billable",
"value": "true",
"created_at": "...",
"updated_at": "..."
},
{
"id": "...",
"org_id": "...",
"entity_type": "time_entry",
"entity_id": "...",
"key": "client",
"value": "acme-corp",
"created_at": "...",
"updated_at": "..."
}
]
Search for all entities with a specific tag:
curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/tags/search?key=billable&value=true" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"items": [
{
"id": "...",
"org_id": "...",
"entity_type": "time_entry",
"entity_id": "...",
"key": "billable",
"value": "true",
"created_at": "...",
"updated_at": "..."
}
],
"next_cursor": null,
"has_more": false
}
CLI shortcut: alloy time log --ticket FIN-1 --duration 2h --type Coding
MCP tools: Use log_time, get_time_report, submit_time_entry,
approve_time_entry, and get_capitalization_report for programmatic
access through AI assistants.
Learn More
- Projects & Tickets — create the tickets you log time against
- Sprints & Boards — track time within sprint iterations
- Teams, Roles & Permissions — control who can approve time entries
- Quarterly Finance Reporting (Playbook) — step-by-step quarterly close process for finance teams
- For Finance Teams — role-based guide for CFOs and finance leads