Milestones API
Milestones are project-scoped planning units that group test runs and track release progress. Each milestone has a server-assigned ULID, a readable per-project ID (M-3), a status lifecycle (upcoming → active → completed | archived), optional date fields, and an optional parent for 1-level hierarchies.
Create
Section titled “Create”/api/v1/projects/{projectId}/milestones{projectId} is the project code (for example ACME).
Request body
Section titled “Request body”| Field | Required | Notes |
|---|---|---|
name | yes | Non-empty string, max 255 characters. Must be unique among live milestones in the project |
description | no | Free-text description, max 2000 characters |
status | no | One of upcoming, active, completed, archived. Defaults to upcoming |
startAt | no | Unix epoch milliseconds. Must be ≤ dueAt when both are provided |
dueAt | no | Unix epoch milliseconds |
forecastAt | no | Unix epoch milliseconds |
parentId | no | ULID of the parent milestone in the same project (1 level only) |
Client-supplied ulid fields are rejected.
Duplicate live name within the project returns 409 conflict. Date ordering violation (dueAt < startAt) or missing required fields return 422 validation_failed.
Response
Section titled “Response”201 with the created milestone object. See Response fields.
Example
Section titled “Example”curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"v2.0 Release","status":"upcoming","startAt":1700000000000,"dueAt":1700600000000}' \ "https://app.probara.net/api/v1/projects/ACME/milestones"/api/v1/projects/{projectId}/milestonesReturns the live (non-deleted) milestones for the project, ordered by ULID ascending. Page-based pagination.
Query parameters
Section titled “Query parameters”| Parameter | Notes |
|---|---|
page | Page number (1-based, default 1) |
pageSize | Items per page (default 50, max 200) |
q | Case-insensitive substring search on milestone name |
status | Filter by exact status: upcoming, active, completed, or archived |
view | Response shape: flat (default) or tree. See List views |
List views
Section titled “List views”The view parameter selects the response shape. It is additive — omitting it (or passing flat) returns the exact same response as before.
view | items | total | Pagination unit |
|---|---|---|---|
flat (default) | Every live milestone (parents and children as siblings) | Count of all live milestones | Individual milestone |
tree | Parent milestones only (those with no parent), each carrying a children array of its live direct children | Count of parent milestones only (children are never counted) | Parent milestone |
In tree mode:
itemscontains only parents. Each parent item is the standard milestone object plus achildrenarray.- Each entry in
childrenis a plain milestone object (the same response shape); children never carry their ownchildrenkey (the hierarchy is one level). qandstatusfilter parents only. A parent matched by the filter shows all of its live children regardless of the children’s own status.totalis the parent count, so the pager (X–Y of Z) counts parent sections. A parent and its children are never split across pages.- A parent with no children has
children: [].
Response
Section titled “Response”200 with { items, page, pageSize, total }. In flat mode each item follows the response shape; in tree mode each item is that shape extended with a children array (see List views).
Example
Section titled “Example”# Flat (default)curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/projects/ACME/milestones?status=active"
# Grouped parent sections with nested childrencurl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/projects/ACME/milestones?view=tree"Example tree response (truncated):
{ "items": [ { "ulid": "01JPARENT...", "name": "v2.4 Release", "status": "active", "children": [{ "ulid": "01JCHILD...", "name": "Auth 2.4", "status": "active" }] } ], "page": 1, "pageSize": 50, "total": 1}/api/v1/milestones/{milestoneUlid}Returns a single live milestone scoped to the active organization, including real-time progress aggregates.
Returns 404 not_found when the milestone is outside the active organization or has been deleted.
Example
Section titled “Example”curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/milestones/01JXXXXXXXXXXXXXXXXXXXXXXXXX"Get by display ID
Section titled “Get by display ID”/api/v1/projects/{projectId}/milestones/by-display-id/{displayId}Returns the same milestone object as GET /api/v1/milestones/{milestoneUlid}, addressed by its readable ID. {projectId} is the project code and scopes the lookup; {displayId} is M-{milestoneNumber} (for example M-3) with no project prefix.
422 validation_failed— malformed ID (M-0,M-01,M-x) or another resource’s prefix (R-3).404 not_found— no milestone with that number in this project, or the milestone has been deleted.
Milestone numbers start at 1, increase with every milestone created in the project, and are never reused. The web app uses the same ID in its URLs, for example /projects/ACME/milestones/M-3.
Example
Section titled “Example”curl -sS \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/projects/ACME/milestones/by-display-id/M-3"Update
Section titled “Update”/api/v1/milestones/{milestoneUlid}Partial update. At least one field must be provided; an empty body returns 422 validation_failed.
Request body
Section titled “Request body”| Field | Required | Notes |
|---|---|---|
name | no | Non-empty string, max 255 characters |
description | no | Max 2000 characters, or null to clear |
status | no | Target status. Must follow valid status transitions (see Status lifecycle) |
startAt | no | Unix epoch milliseconds, or null to clear |
dueAt | no | Unix epoch milliseconds. Must be ≥ startAt when both are set |
forecastAt | no | Unix epoch milliseconds, or null to clear |
parentId | no | ULID of the new parent in the same project, or null to detach |
Invalid status transitions return 422 validation_failed. Duplicate live name returns 409 conflict. Returns 404 not_found when the milestone is outside the active organization.
Example
Section titled “Example”curl -sS -X PATCH \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"status":"active","dueAt":1700700000000}' \ "https://app.probara.net/api/v1/milestones/01JXXXXXXXXXXXXXXXXXXXXXXXXX"Delete
Section titled “Delete”/api/v1/milestones/{milestoneUlid}Soft-deletes the milestone. Returns 204 on success. Subsequent GET returns 404 not_found and the milestone is excluded from list results.
Deletion atomically unassigns all linked test runs, test cases, and child milestones.
Returns 404 not_found when the milestone is outside the active organization.
Example
Section titled “Example”curl -sS -X DELETE \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ "https://app.probara.net/api/v1/milestones/01JXXXXXXXXXXXXXXXXXXXXXXXXX"Status lifecycle
Section titled “Status lifecycle”Milestones follow a directed state machine. Only the transitions listed below are accepted; all others return 422 validation_failed.
| From | Allowed transitions |
|---|---|
upcoming | active, archived |
active | completed, archived |
completed | active (reopen), archived |
archived | — (terminal) |
Transitioning to completed automatically sets completedAt to the current timestamp. Transitioning away from completed clears completedAt.
Response fields
Section titled “Response fields”All mutating endpoints (POST, PATCH) and GET /api/v1/milestones/{milestoneUlid} return a single milestone object.
| Field | Type | Notes |
|---|---|---|
ulid | string (ULID) | Server-assigned unique identifier |
projectId | string | The project code this milestone belongs to |
milestoneNumber | number | Per-project number; starts at 1, never reused |
displayId | string | Readable ID M-{milestoneNumber} (e.g. M-3), used in lookups and web URLs |
name | string | Display name |
description | string | null | Optional description |
status | string | Current status: upcoming, active, completed, or archived |
startAt | number | null | Unix epoch milliseconds |
dueAt | number | null | Unix epoch milliseconds |
forecastAt | number | null | Unix epoch milliseconds |
completedAt | number | null | Set automatically when status transitions to completed |
parentId | string (ULID) | null | Parent milestone ULID, or null for top-level |
progress.totalRuns | number | Total test runs linked to this milestone |
progress.closedRuns | number | Closed (finished) test runs |
progress.percentComplete | number | closedRuns / totalRuns × 100, rounded; 0 when no runs |
progress.passRate | number | passed / totalResults × 100, rounded; 0 when no results |
progress.passed | number | Test case results that passed across all linked runs |
progress.failed | number | Test case results that failed |
progress.blocked | number | Test case results that were blocked |
progress.skipped | number | Test case results that were skipped |
progress.untested | number | Test cases not yet executed |
author | object | null | User who created the milestone, or null for API-token-created and legacy milestones |
author.kind | string | Always "user" when present |
author.ulid | string (ULID) | User identifier |
author.displayName | string | Full name or email of the author |
author.avatarKey | string | null | Storage key for the author’s avatar image |
childCount | number | Number of direct child milestones |
testCaseCount | number | Number of test cases linked to this milestone |
createdAt | number | Unix epoch milliseconds |
updatedAt | number | Unix epoch milliseconds |
Audit events
Section titled “Audit events”Milestone mutations emit the following events on the organization activity feed:
| Action | Trigger |
|---|---|
milestone.created | Milestone created |
milestone.updated | Any field updated via PATCH |
milestone.status_changed | Status field changed via PATCH |
milestone.deleted | Milestone soft-deleted |
See Audit events.