Skip to content

Test Plans API

Test plans are project-scoped collections of selected test cases. A plan holds an ordered selection of live cases; from that selection you can generate a test run that snapshots the cases at the moment of generation. Each plan has a server-assigned ULID, a monotonically allocated plan number within the project, and an optional description.

POST/api/v1/projects/{projectId}/plans

{projectId} is the project code (for example ACME).

FieldRequiredNotes
nameyesNon-empty string, max 200 characters. Must be unique among live plans in the project
descriptionnoFree-text description, max 2000 characters
statusnoInitial status: draft, active, or completed. Defaults to draft when omitted

Client-supplied ulid or planNumber fields are rejected. Duplicate live name within the project returns 409 conflict. Missing or invalid fields return 422 validation_failed.

201 with the created plan object. See Response fields.

Terminal window
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Sprint 24","description":"Feature release"}' \
"https://probara.net/api/v1/projects/ACME/plans"
GET/api/v1/projects/{projectId}/plans

Returns the live (non-deleted) plans for the project, ordered by plan number ascending. Page-based pagination.

ParameterNotes
pagePage number (1-based, default 1)
pageSizeItems per page (default 20, max 200)

200 with { items, page, pageSize, total }. Each item follows the response shape.

Terminal window
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/projects/ACME/plans?page=1&pageSize=20"
GET/api/v1/plans/{planUlid}

Returns a single live plan scoped to the active organization, including the current case count.

Returns 404 not_found when the plan is outside the active organization or has been deleted.

Terminal window
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000"
PATCH/api/v1/plans/{planUlid}

Updates one or more fields of a live plan. At least one field must be provided. Duplicate live name returns 409 conflict.

FieldRequiredNotes
namenoNew name (max 200 characters). Must be unique among live plans in the project
descriptionnoUpdated description (max 2000 characters). Send null to clear
statusnoNew lifecycle status: draft, active, or completed. Any transition between distinct statuses is valid. Sending the plan’s current status is a no-op (accepted, no event emitted). An unknown status literal returns 422 validation_failed
milestoneIdnoULID of a live milestone in the plan’s project to link, or null to clear the existing link. A ULID that does not resolve to a live milestone in the plan’s project returns 404 not_found and leaves the plan unchanged. A milestone from another project or organization is also 404 not_found

An empty body (no fields) returns 422 validation_failed.

200 with the updated plan object. See Response fields.

  • All transitions between draft, active, and completed are valid in any direction.
  • There are no terminal states: a completed plan can be moved back to active or draft.
  • A same-value status PATCH (the status field equals the plan’s current status) is accepted as a no-op — no status change is recorded and the plan returns 200 unchanged.
  • An unknown status literal (not one of the three valid values) returns 422 validation_failed.
  • Status is purely manual: the API never auto-advances a plan’s status based on its runs.
Terminal window
# Change status to active
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"active"}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000"
Terminal window
# Link to a milestone
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"milestoneId":"01HZMILESTONE0000000000000"}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000"
Terminal window
# Clear the milestone link
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"milestoneId":null}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000"
Terminal window
# Combined: rename and mark completed
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Sprint 25 Final","status":"completed"}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000"
DELETE/api/v1/plans/{planUlid}

Soft-deletes the plan. The plan is no longer listed or accessible, and its name becomes available for reuse. Existing runs generated from the plan are not affected.

Returns 204 on success, 404 not_found when not found.

Terminal window
curl -sS -X DELETE \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000"
PUT/api/v1/plans/{planUlid}/cases

Replace-set semantics: the entire selection is replaced with the supplied ordered list of case ULIDs. Omitting a case from the list removes it; supplying it in a different position moves it. Send an empty caseUlids array to clear the selection.

You can optionally assign a team member to each case in the same call by supplying caseAssignees. Assignee entries whose caseUlid is not present in caseUlids are silently ignored. All assignee resolution happens before the selection is replaced — a bad assigneeUlid leaves the current selection unchanged.

FieldRequiredNotes
caseUlidsyesOrdered array of live test case ULIDs. Duplicate ULIDs rejected (422). Cases must belong to the plan’s project
caseAssigneesnoArray of { caseUlid, assigneeUlid } pairs. Each assigneeUlid must be a member of the active organization — a cross-org assigneeUlid returns 404 not_found and leaves the selection unchanged. Entries whose caseUlid is not in caseUlids are ignored. Omitting this field (or omitting an entry for a given case) stores that case unassigned

Returns 404 not_found when any supplied ULID is not a live case in the project, or when any assigneeUlid is not an org member. Returns 422 validation_failed for duplicate ULIDs.

200 with { items } — the new selection in position order. See Selection item fields.

Terminal window
curl -sS -X PUT \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"caseUlids":["01HZCASEA","01HZCASEB","01HZCASEC"]}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/cases"

Example — case selection with per-case assignees

Section titled “Example — case selection with per-case assignees”
Terminal window
curl -sS -X PUT \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"caseUlids": ["01HZCASEA","01HZCASEB","01HZCASEC"],
"caseAssignees": [
{ "caseUlid": "01HZCASEA", "assigneeUlid": "01HZMEMBER000000000000000001" },
{ "caseUlid": "01HZCASEB", "assigneeUlid": "01HZMEMBER000000000000000002" }
]
}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/cases"

01HZCASEC has no entry in caseAssignees and is stored unassigned.

PATCH/api/v1/plans/{planUlid}/cases/{planCaseUlid}

Updates the assignee of a single plan case without modifying the rest of the case selection. This endpoint is the per-case inline update — it does not replace the plan’s case list.

Snapshot semantics: updating a plan case’s assignee does not back-propagate to any runs already generated from the plan. Existing run cases keep the assignee they had at the moment the run was generated.

ParameterDescription
planUlidULID of the plan
planCaseUlidULID of the plan case row (the ulid field from a selection item, not the test case ULID)
FieldRequiredNotes
assigneeUlidyesULID of a live org member, or null to clear the assignee

The body must be exactly one key. Unknown keys return 422 validation_failed.

200 with the updated plan case object (same shape as a selection item). See Selection item fields.

StatusCodeWhen
403 ForbiddenCaller has viewer role
404 Not Foundnot_foundplanUlid or planCaseUlid does not exist or belongs to a different org
404 Not Foundnot_foundassigneeUlid does not resolve to a live org member
422 Unprocessable Entityvalidation_failedMissing assigneeUlid key, non-ULID value, or unknown key
Terminal window
# Assign a member to a plan case
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"assigneeUlid":"01HZMEMBER000000000000000001"}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/cases/01HZPLANCASE000000000000000"
# Clear the assignee (send null)
curl -sS -X PATCH \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"assigneeUlid":null}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/cases/01HZPLANCASE000000000000000"
GET/api/v1/plans/{planUlid}/cases

Returns the current case selection for the plan, ordered by position ascending.

200 with { items }. See Selection item fields.

Terminal window
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/cases"
PUT/api/v1/plans/{planUlid}/configurations

Replace-set semantics: the plan’s entire configuration selection is replaced with the supplied list of configuration value ULIDs. Send an empty configurationUlids array to clear the selection.

Each supplied ULID must resolve to a live (non-deleted) configuration value in the plan’s project. An unresolved, soft-deleted, or cross-project ULID returns 404 not_found and leaves the selection unchanged. Duplicate ULIDs in the request return 422 validation_failed.

FieldRequiredNotes
configurationUlidsyesArray of live configuration value ULIDs. Duplicates rejected (422). Values must belong to the plan’s project

200 with { items } — the new configuration selection. See Configuration selection item fields.

Terminal window
curl -sS -X PUT \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"configurationUlids":["01HZCHROME000000000000000000","01HZWINDOWS00000000000000000"]}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/configurations"
GET/api/v1/plans/{planUlid}/configurations

Returns the plan’s current configuration selection — the set of configuration value ULIDs the plan uses to compute its combination matrix.

200 with { items }. See Configuration selection item fields.

Terminal window
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/configurations"
PUT/api/v1/plans/{planUlid}/excluded-combinations

Replace-set semantics: the plan’s excluded combination keys are replaced with exactly the supplied set. A comboKey that does not match any current candidate combination is accepted and stored as an inert exclusion — it will not cause an error.

FieldRequiredNotes
comboKeysyesArray of combination key strings. A combination key is the sorted, colon-joined list of the combination’s configuration value ULIDs (e.g. 01HZCHROME:01HZWINDOWS). Duplicates rejected (422). Send an empty array to clear all exclusions

200 with { comboKeys } — the plan’s new excluded combination key set.

Terminal window
curl -sS -X PUT \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"comboKeys":["01HZFIREFOX0000000000000000:01HZWINDOWS00000000000000000"]}' \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/excluded-combinations"
GET/api/v1/plans/{planUlid}/combinations

Returns the candidate combinations computed as the cartesian product of the plan’s current configuration selection (one value per participating group). Each combination carries its comboKey, the ordered per-group values, and an included flag (false when the comboKey is in the plan’s excluded set). The response also returns the total cartesian count and the count of included combinations.

A plan with no configuration selection returns exactly one candidate combination (the empty combination, totalCount = 1, includedCount = 1).

200 with:

FieldTypeNotes
itemsarrayCandidate combination objects. See Combination item fields
totalCountnumberTotal number of candidate combinations (including excluded)
includedCountnumberNumber of combinations with included = true
Terminal window
curl -sS \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
"https://probara.net/api/v1/plans/01HZPLANULID00000000000000/combinations"

There is no POST /api/v1/plans/{planUlid}/runs. Create a run for a plan with POST /api/v1/projects/{projectId}/runs, passing planUlid and, for a configured plan, the combination’s configurationUlids. This call always creates exactly one run — link a plan, seed its cases, and tag one combination, all in a single request. To cover several combinations, call it once per combination.

When planUlid is supplied and caseUlids is omitted, the run’s case selection and per-case assignees are seeded from the plan’s current selection, following this precedence (first match wins):

  1. An explicit per-case entry in caseAssignees (the request body)
  2. The plan’s stored per-case assignee for that case
  3. defaultAssigneeUlid from the request body
  4. Unassigned (null)

A plan assignee who is no longer a member of the organization at request time seeds to null (unassigned) without failing the request.

Terminal window
curl -sS -X POST \
-H "Authorization: Bearer $PROBARA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Smoke — Chrome","planUlid":"01HZPLANULID00000000000000","configurationUlids":["01HZCFGVALCHROME0000000000"]}' \
"https://probara.net/api/v1/projects/PROJ/runs"

The plan’s runs are listed most-recent first via GET /api/v1/plans/{planUlid}/runs.

The plan detail (GET /api/v1/plans/{planUlid}) and the PATCH response return the full plan object. The plan list (GET /api/v1/projects/{projectId}/plans) returns the same shape per item but omits progress.

FieldTypeNotes
ulidstringServer-assigned ULID for the plan
projectUlidstringULID of the project the plan belongs to
planNumbernumberMonotonically allocated number within the project
namestringPlan name
descriptionstring | nullOptional description
authorobject | nullAuthorship snapshot (see below). null for legacy rows
caseCountnumberCurrent count of live cases in the selection
statusstringCurrent lifecycle status: draft, active, or completed
milestoneUlidstring | nullULID of the linked milestone, or null when no milestone is linked
milestoneobject | nullLinked milestone reference ({ ulid, name }), or null when no milestone is linked
progressobjectComputed progress rollup (detail and PATCH responses only — see below)
createdAtnumberCreation timestamp (Unix epoch milliseconds)
updatedAtnumberLast-update timestamp (Unix epoch milliseconds)
FieldTypeNotes
kind"user" | "api_token"How the plan was created
ulidstringULID of the user or API token
displayNamestringDisplay name (user kind only)
avatarKeystring | nullAvatar object key (user kind only)
namestringToken name (api_token kind only)

progress is computed at read time from the plan’s runs. It is present on the plan detail (GET /api/v1/plans/{planUlid}) and the PATCH response. The plan list does not carry progress.

FieldTypeNotes
totalRunsnumberTotal number of runs generated from this plan
closedRunsnumberNumber of those runs that are in closed state
countsobjectOverall outcome counts summed across all the plan’s runs (see below)
byConfigurationarrayPer-configuration breakdown rows (see below). Empty when the plan has no runs

Outcome counts (counts and per-breakdown-row counts)

Section titled “Outcome counts (counts and per-breakdown-row counts)”
FieldTypeNotes
passednumberSum of passed test results
failednumberSum of failed test results
blockednumberSum of blocked test results
skippednumberSum of skipped test results
untestednumberSum of untested (pending) test results

These are summed from the denormalized per-status counters maintained on each test_run row. No re-scanning of individual results occurs at read time.

Per-configuration breakdown (byConfiguration items)

Section titled “Per-configuration breakdown (byConfiguration items)”

Each item in byConfiguration corresponds to one configuration combination (or the unconfigured bucket):

FieldTypeNotes
comboKeystringCombination key: the run’s configuration value ULIDs sorted ascending and joined with :. Empty string ("") for the unconfigured bucket (runs with no configuration tags)
valuesarrayOrdered per-group value entries ({ groupName, valueName }). Empty array for the unconfigured bucket
totalRunsnumberNumber of runs in this combination
closedRunsnumberNumber of those runs that are closed
countsobjectOutcome counts for this combination (same shape as the top-level counts)

The sum of all byConfiguration[*].counts equals the top-level counts. Runs with no configuration tags appear in a single unconfigured bucket (empty comboKey, empty values).

Returned by GET /plans/{planUlid}/cases and PUT /plans/{planUlid}/cases.

FieldTypeNotes
ulidstringServer-assigned ULID of the selection row
caseUlidstringULID of the referenced test case
displayIdstringDisplay ID of the case (e.g. TC-42)
titlestringCurrent title of the test case
suiteNamestring | nullSuite name for grouping, or null
positionnumber0-based position within the plan’s selection
assigneeUlidstring | nullULID of the member assigned to this case in the plan, or null when the case is unassigned

Each item in the GET /plans/{planUlid}/configurations and PUT /plans/{planUlid}/configurations responses:

FieldTypeNotes
configurationUlidstringULID of the configuration value
groupUlidstringULID of the configuration group this value belongs to
groupNamestringDisplay name of the group (e.g. Browser)
valueNamestringDisplay name of the value (e.g. Chrome)

Each item in the GET /plans/{planUlid}/combinations response:

FieldTypeNotes
comboKeystringDeterministic combination key: the combination’s configuration value ULIDs sorted ascending and joined with :. Use this key in PUT /excluded-combinations to exclude the combination
valuesarrayPer-group value entries, ordered by group position. Each entry has groupUlid, groupName, configurationUlid, and valueName
includedbooleantrue when this combination is not in the plan’s excluded set; false when excluded