Defects API
Defects are project-scoped bug records with per-project sequential numbers (defectNumber, shown as D-<n> in the UI). The reporter is always derived from the authenticated actor on create and cannot be set in the request body.
Create
Section titled “Create”/api/v1/projects/{projectId}/defects{projectId} is the project code (for example ACME).
Request body
Section titled “Request body”| Field | Required | Notes |
|---|---|---|
title | yes | Non-empty string |
description | no | Markdown text |
assigneeUlid | no | Organization member ULID |
tags | no | String array |
milestoneUlid | no | Milestone in the same project |
customFieldValues | no | Array of { fieldUlid, value } writes for visible defect fields. The seeded Severity and Priority system fields are required with org defaults (major / medium option ULIDs); omit unchanged defaults and the server persists them. Other omitted keys are satisfied at create time when the field has a non-empty stored default; otherwise create returns 422 validation_failed. |
reporter, resolvedAt, and closedAt are rejected if sent by the client.
Example
Section titled “Example”curl -sS -X POST \ -H "Authorization: Bearer $PROBARA_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"title":"Login fails with expired password"}' \ "https://probara.net/api/v1/projects/ACME/defects"/api/v1/projects/{projectId}/defectsPage-based pagination (page, pageSize), newest first. Response shape matches other table list endpoints (items, page, pageSize, total), where total counts every defect matching the active filters and q. Each list item includes the defect fields plus occurrenceCount, lastSeenAt (aggregated from linked run attempts), and systemFieldValues (severity/priority system entries with option enrichment only). When fields is omitted, list items do not include customFieldValues.
Pass fields as a comma-separated list of defect custom-field ULIDs visible in the project to embed customFieldValues on each item (stored value or materialized default, same shape as the detail endpoint). Requesting a field outside the project scope returns 422 validation_failed.
Query filters
Section titled “Query filters”Filters compose with AND semantics across parameters and are evaluated server-side.
The set-valued parameters severity, priority, and assigneeUlid take a
comma-separated set of ULIDs and OR-combine within each parameter; a single value
is accepted as a one-element set, so older single-value URLs keep working.
| Parameter | Notes |
|---|---|
status | Comma-separated set, e.g. open,in_progress |
severity | Comma-separated set of option ULIDs of the org’s defect_severity system field (OR within; matches defects holding any) |
priority | Comma-separated set of option ULIDs of the org’s defect_priority system field (OR within) |
assigneeUlid | Comma-separated set of organization member ULIDs (OR within); a ULID that resolves to no member is ignored, and the filter matches nothing only when none resolve |
q | Title substring, or defect number via D-<n> or a bare integer |
aging | Open-defect age bucket: d0_7, d7_14, d14_30, d30plus (by created_at) |
fields | Comma-separated custom-field ULIDs to embed per item (customFieldValues) |
cf | Repeatable custom-field filter: <fieldUlid>:<value[,value…]> (see below) |
Invalid status values return 422 validation_failed.
Custom-field filters (cf)
Section titled “Custom-field filters (cf)”Repeat the cf query parameter once per field. Multiple cf entries AND-compose. Within one field, comma-separated values OR-compose.
Supported field types: select, select_multi, checkbox, and user_picker. Other types (including paragraph) return 422 validation_failed. Filters match stored value rows only — a materialized default on read does not satisfy an option filter until persisted.
| Value token | Meaning |
|---|---|
| Option ULID | Defect stored with that option (select) or containing it (select_multi) |
true / false | Checkbox stored value |
| Member ULID | user_picker stored member |
empty | No stored row for the field (including defects that would show a default on detail) |
Example — defects in Staging or Production (same field, comma OR):
GET /api/v1/projects/ACME/defects?cf=01HENVFIELD:01HSTAGING,01HPRODExample — defects with no Environment value or Staging, and checkbox Risk accepted:
GET /api/v1/projects/ACME/defects?cf=01HENVFIELD:empty,01HSTAGING&cf=01HRISKFIELD:trueExample — embed Environment and Owner columns:
GET /api/v1/projects/ACME/defects?fields=01HENVFIELD,01HOWNERFIELDDashboard metrics
Section titled “Dashboard metrics”/api/v1/projects/{projectId}/defects/metricsSingle response for the defects list strip and sidebar badge. All figures use the
active organization and project scope; cross-tenant requests return 404 not_found.
| Field | Definition |
|---|---|
totalOpen | Defects in open or in_progress |
openBySeverity | Object with options (array of { optionUlid, systemKey, name, icon, color, count } in option order, zero counts included) and unset (open defects with no severity value row) |
aging | Open population bucketed by age since created_at: d0_7, d7_14, d14_30, d30plus |
reopenRate | Distinct defects with ≥1 defect.reopened audit event ÷ distinct defects ever resolved (defect.status_changed with to of resolved or closed); null when the denominator is 0 |
mttrMs | Mean resolved_at − created_at over currently resolved/closed defects with non-null resolved_at; null when none |
byMilestone | Open population per milestone (ulid, name, count) plus unassigned |
Run-linked defects
Section titled “Run-linked defects”/api/v1/runs/{runUlid}/defectsLists distinct defects linked to any attempt in the run (join defect_result_links →
test_results for the run). Each item includes public defect fields plus
linkCountInRun (links within this run only — links on other runs are excluded), and systemFieldValues for severity/priority.
Returns { items: [...] } (no pagination). Cross-tenant or unknown runs return
404 not_found. Works for open and closed runs.
Detail
Section titled “Detail”/api/v1/defects/{defectUlid}Returns the full defect payload including vocabulary fields, people ULIDs, tags, milestone, duplicate-of reference, lifecycle timestamps, derived occurrence summary fields (occurrenceCount, lastSeenAt, affectedCaseCount), and embedded customFieldValues for every defect-scoped field visible in the project (stored value or materialized default, with option enrichment when applicable). The project defects list includes customFieldValues only when requested via fields.
Custom field values
Section titled “Custom field values”Defect-scoped definitions are managed under GET/POST/PATCH/DELETE /api/v1/orgs/{orgUlid}/custom-fields with entity: defect. Values are stored per (field, defect) pair.
Replace all visible values
Section titled “Replace all visible values”/api/v1/defects/{defectUlid}/custom-field-valuesBody: { "values": [ { "fieldUlid": "...", "value": ... }, ... ] } — one entry per visible field. Required fields must be present in the write set. Emits defect.updated with metadata.customFieldChanges (not a separate custom_field.* event).
Patch one field
Section titled “Patch one field”/api/v1/defects/{defectUlid}/custom-field-values/{fieldUlid}Body: { "value": ... }. Clears the value when null is sent for nullable types; required fields reject empty clears with 422 validation_failed. Returns the full embedded customFieldValues array for the defect.
Update
Section titled “Update”/api/v1/defects/{defectUlid}Partial update of title, description, status, resolution, assigneeUlid, tags, milestoneUlid, and duplicateOfUlid. Severity and priority are edited via the custom-field value endpoints, not this patch.
Status transitions follow a guided map (open/in_progress → resolved/closed; resolved → closed/open; closed → open). Illegal transitions return 422 validation_failed with field path status. Same-status patches apply other fields without emitting a status event.
- Transitioning to
resolvedorclosedrequires aresolution. resolution = duplicaterequiresduplicateOfUlid; chains flatten to the canonical root on write.- Reopening to
openfromresolvedorclosedclearsresolution,duplicateOfUlid, and managed timestamps; emitsdefect.reopenedinstead ofdefect.status_changed. duplicateOfUlidmust reference another defect in the same project when used.
Requires organization role owner, admin, or member; a viewer receives 403 forbidden before any write.
Returns 404 not_found when the defect is outside the active organization.
Delete
Section titled “Delete”/api/v1/defects/{defectUlid}Hard-deletes the defect. Returns 204 on success; subsequent GET returns 404 not_found.
Link to test results
Section titled “Link to test results”Defects can be linked to immutable run attempts (test_results rows). Links are unique per (defect, result) pair; repeating the same request returns the existing link with 200.
Create or re-link
Section titled “Create or re-link”/api/v1/defects/{defectUlid}/result-links| Field | Required | Notes |
|---|---|---|
resultUlid | yes | Result from a run in the same project as the defect |
stepSnapshotUlid | no | Frozen step snapshot belonging to that result |
Returns 201 on first link, 200 when the pair already exists.
List occurrences
Section titled “List occurrences”/api/v1/defects/{defectUlid}/result-linksCursor pagination (limit, cursor). Each item includes the link, linker, result status/executedAt, and run/test-case context.
Unlink
Section titled “Unlink”/api/v1/defects/{defectUlid}/result-links/{linkUlid}Returns 204. The link must belong to the referenced defect.
Suggestions for a test case
Section titled “Suggestions for a test case”/api/v1/test-cases/{caseUlid}/defectsLists defects previously linked to any result of the test case, with per-case occurrenceCount, lastSeenAt, and systemFieldValues (severity/priority). Open/in-progress defects are ordered before resolved/closed ones.
Affected test cases
Section titled “Affected test cases”/api/v1/defects/{defectUlid}/affected-casesCursor pagination (limit, cursor). Returns distinct test cases linked through result associations. Each item includes testCaseUlid, testCaseTitle, caseNumber, displayId (the case’s repository reference, {PROJECT_CODE}-{caseNumber}), occurrenceCount, lastLinkedAt, latestResult (overall newest execution for the case, or null when never executed), and verification.
When the defect status is resolved, verification is one of:
| Value | Meaning |
|---|---|
verified | Latest result is passed with executedAt after resolvedAt |
failed_after_resolve | Latest result is failed or blocked after resolvedAt |
pending | No qualifying execution after resolution |
For non-resolved defects, verification is null. Returns 404 not_found cross-tenant.
Audit events
Section titled “Audit events”Defect mutations emit defect.created, defect.updated, defect.status_changed, defect.reopened, defect.deleted, defect.linked, and defect.unlinked on the organization activity feed (link events also appear on the run feed via run_id). Evidence and discussion mutations also emit defect.attachment_added, defect.attachment_deleted, defect.commented, defect.comment_updated, and defect.comment_deleted. Duplicate resolutions include duplicateOfUlid and a D-<n> label in defect.status_changed metadata. See Audit events.
Per-defect history
Section titled “Per-defect history”/api/v1/defects/{defectUlid}/audit-eventsCursor pagination (limit, cursor) over events whose entityType is defect and whose entityUlid matches the defect. Response shape matches the run audit endpoint (events, nextCursor). Returns 404 not_found when the defect is outside the active organization.
Evidence attachments
Section titled “Evidence attachments”Image evidence is stored under the organization R2 prefix. Allowed types: PNG, JPEG, WebP. Max 10 MiB per file, up to 10 files per upload request. Uploads charge the organization defect_attachment storage counter.
Viewers may list attachments; only members and above may upload or delete.
/api/v1/defects/{defectUlid}/attachmentsReturns { attachments: [...] } in upload order (oldest first). Each item includes ulid, mime, dimensions, byteSize, originalFilename, uploadedBy, url, thumbUrl, and createdAt.
Upload
Section titled “Upload”/api/v1/defects/{defectUlid}/attachmentsmultipart/form-data with one or more file parts. Returns 201 with { attachments: [...] }. Disallowed MIME types or oversize files return 422 validation_failed. Emits defect.attachment_added.
Delete
Section titled “Delete”/api/v1/defects/{defectUlid}/attachments/{attachmentUlid}Returns 204. Removes the row, both R2 objects, and refunds storage bytes. Emits defect.attachment_deleted. An attachment ULID belonging to another defect returns 404 not_found.
Defect deletion removes all evidence objects and refunds summed bytes before the cascade.
Discussion comments
Section titled “Discussion comments”Comments are plain text (trimmed, non-empty, max 5000 characters) and support one level of threaded replies, pinning, and @mention resolution. The defect comment surface is structurally identical to test case comments — see that page for the full reference (reply semantics, pinning rules, mention resolution, response shape, and cascade delete). This section lists only the defect-scoped paths and audit actions.
Viewers may list comments; only members and above may create, edit, delete, or pin/unpin. Listing is cursor-paginated over top-level comments (parentUlid absent), defaulting to oldest-first; each item’s inline replies[] stays chronological regardless of sort.
/api/v1/defects/{defectUlid}/commentsQuery: limit (default 50, max 200), cursor, sort (oldest | newest, default oldest). Each item carries parentUlid, pinnedAt, pinnedBy, replyCount, mentions, and inline replies[]; the response’s pinned[] sibling holds the complete pinned set for the defect. Replies are not enumerated in the flat items[] — see test case comments for the full response shape and the replies caveat.
Create
Section titled “Create”/api/v1/defects/{defectUlid}/comments| Field | Required | Notes |
|---|---|---|
body | yes | Trimmed text, 1–5000 characters |
parentUlid | no | Replies to an existing top-level comment on the same defect (depth-1 only) |
Returns 201. Emits defect.commented with a 140-character excerpt.
/api/v1/defects/{defectUlid}/comments/{commentUlid}| Field | Required | Notes |
|---|---|---|
body | yes | Same validation as create |
Author only — admins cannot edit another member’s comment (403 forbidden). Sets editedAt on success and re-resolves mentions. Emits defect.comment_updated.
Delete
Section titled “Delete”/api/v1/defects/{defectUlid}/comments/{commentUlid}Returns 204. The author may delete their own comment. Organization admins may delete any comment. Other members receive 403. Deleting a top-level comment cascades to its replies, purging their attachments. Emits defect.comment_deleted (once for the deleted comment, plus once per cascaded reply).
Pin / unpin
Section titled “Pin / unpin”POST / DELETE /api/v1/defects/{defectUlid}/comments/{commentUlid}/pin
Any member or above may pin or unpin any top-level comment, regardless of authorship; viewers receive 403. Pinning a reply returns 422 validation_failed. There is no cap on pinned comments per defect. Emits defect.comment_pinned / defect.comment_unpinned.