Skip to main content

Milestones

Milestones are first-class date-based checkpoints within a project. They represent key delivery dates, contractual deadlines, or payment gates that the team tracks independently of individual tasks.

Data Model

Each milestone belongs to a project and optionally to a stage. Key fields:

  • title — human-readable name (required)
  • dueDate — the deadline (required, ISO-8601 datetime)
  • status — derived automatically: PENDING, COMPLETED, or OVERDUE
  • completedAt — when set, status becomes COMPLETED; when cleared, status reverts to PENDING or OVERDUE based on dueDate
  • stageId — optional association with a project stage
  • paymentAmount / paymentCurrency / paymentStatus — optional payment metadata (no workflow enforced)

Status Derivation

Status is never set directly. It is computed from completedAt and dueDate:

completedAtdueDate vs nowResulting status
non-nullanyCOMPLETED
nullfuturePENDING
nullpast or nowOVERDUE

Attempting to set status directly via PATCH returns HTTP 400.

API

All routes are project-scoped and require both:

  1. Project access — creator, active collaborator, or an internal-org member with org-wide projects.read.
  2. The corresponding tasks.* permission via requirePermission:
    • tasks.read for GET /milestones and GET /milestones/{milestoneId}
    • tasks.create for POST /milestones
    • tasks.update.any for PATCH /milestones/{milestoneId}
    • tasks.delete.any for DELETE /milestones/{milestoneId}

Dedicated milestones.* permission strings are tracked as a follow-up.

MethodPathDescription
GET/api/projects/{id}/milestonesList milestones
POST/api/projects/{id}/milestonesCreate milestone
GET/api/projects/{id}/milestones/{milestoneId}Get milestone
PATCH/api/projects/{id}/milestones/{milestoneId}Update milestone
DELETE/api/projects/{id}/milestones/{milestoneId}Soft-delete

Soft-deleted milestones are excluded from list and get responses.

Overdue Sweep (Cron)

A daily cron job (/api/cron/milestones/overdue, 02:00 UTC) transitions all past-due PENDING milestones to OVERDUE and publishes a projects.milestone.overdue domain event for each transitioned milestone.

The sweep is idempotent — running it multiple times produces no duplicate transitions or outbox rows.

Events

EventTrigger
projects.milestone.overdueCron sweep transitions to OVERDUE

See the events reference for the full payload schema.