Skip to content

List your test cases

GET
/v1/test-cases
curl --request GET \
--url 'https://beta-api.plune.ai/v1/test-cases?limit=200&stability=0&priorityMin=low&defects=1&flaky=1&muted=1&covered=1&starred=1&notRunDays=30' \
--header 'Authorization: Bearer <token>'

The query string is the filter (B-7 AC-11): a repeated parameter is OR inside its dimension, different parameters are AND. Absent means every case. The order is the tree as the dashboard draws it: suite by suite down the tree, a folder’s child nodes by position before the node’s own cases, each suite’s cases by position, then creation, then id; unfiled cases last. A page is therefore the top of the tree, and the suite at its bottom may be cut by the page edge.

PAGED as of #1391 (D4): a response carries at most limit cases (200 at most, and 200 when none is sent) and a nextCursor; a client that read testCases and never looked at nextCursor now sees only the first page. The filter is applied before the page; matching counts what it admits, total every case. A cursor that is not one is REFUSED with 400 rather than answered with the start of the list.

Sends a Server-Timing header (W3C): where the server spent the time, in milliseconds to a tenth — DevTools lists it under Network → Timing. auth is the token or session and the project, counted from the start of the request; rest is the page of cases and their counts; stability is the badges of that page — the read of its cases’ histories and their scoring, asked once the page is known. total is the whole answer, before it is sent. The names are fixed and carry nothing of yours.

limit
integer
default: 200 >= 1 <= 200

Page size; clamped into the range, the maximum when absent.

cursor
string

nextCursor from the previous page. Omit for the first page.

stability
string
Allowed values: 0 false

0 or false: skip the stability query and answer stability: {} — for a caller that wants the cases or a count, not the badges. Anything else is the default.

suiteId
Array<string>

A suite: its cases. A folder: the cases of every suite beneath it. Repeat for several.

tag
Array<string>

Verbatim — Smoke is not smoke. Repeat for any-of.

priority
Array<string>
Allowed values: low normal important high critical
priorityMin
string
Allowed values: low normal important high critical

This rank and above — high means high and critical.

type
Array<string>
Allowed values: manual automated eval
state
Array<string>
Allowed values: draft in_review active detached deprecated
assignee
Array<string>

Who is to run it (B8-team T4): me — the caller; none — nobody; otherwise a member’s userId. Repeat for OR. An id nobody holds matches nothing.

q
string

Looked for in the title, the description, the precondition, every step (action and expected result), the case’s own expected result and the parameters table (names and cells), case-insensitively (#1255). Text, not a pattern.

defects
string
Allowed values: 1

1: only the cases that carry at least one defect link (#1064), together with the other filters. Anything else is no filter.

flaky
string
Allowed values: 1 true 0 false

1 or true: only the cases marked flaky (isFlaky, #1246); 0 or false: only the others — together with the other filters. Any other value is a 400 that names the parameter.

muted
string
Allowed values: 1 true 0 false

1 or true: only the muted cases (isMuted, #1246); 0 or false: only the ones that count — together with the other filters. Any other value is a 400 that names the parameter.

requirement
Array<string>

Only the cases linked to this requirement (#1268); repeat for OR. An id nobody holds matches nothing.

covered
string
Allowed values: 1 true 0 false

1 or true: only the cases linked to at least one requirement that is not archived (#1268); 0 or false: only the cases linked to none — cases need no requirement, so this works with only some of them linked. Together with the other filters. Any other value is a 400 that names the parameter.

starred
string
Allowed values: 1 true 0 false

1 or true: only the cases in a starred suite or under a starred folder (#1255); 0 or false: only the others, a case with no suite included. Together with the other filters. Any other value is a 400 that names the parameter.

health
Array<string>
Allowed values: never_run ever_failing flaky

The list a health widget on Analytics leads to (#1260); repeat for OR, together with the other filters. never_run: live manual and automated cases (not evals) with no result in a live run for notRunDays days, or no result at all. ever_failing: cases whose history holds at least 3 verdicts and every one is a failure (failed or broken; skipped and blocked are no verdict). flaky: the cases Analytics counts in kpis.flaky — flaky as measured over the history, not the isFlaky flag. Any other word is a 400 that names the values.

notRunDays
integer
default: 30 >= 1 <= 365

How many days back health=never_run looks for a result (#1260); 30 when absent. A whole number outside 1..365 is a 400 that names the parameter.

estimate
Array<string>
Allowed values: none lt5 5to30 gt30

Only the cases whose estimate (#1881) is in one of these buckets (#1905); repeat the key or separate with commas for OR, together with the other filters. none: nobody estimated the case (no estimate, never a zero one). lt5: under 5 minutes (0..299 s). 5to30: 5 to 30 minutes (300..1800 s). gt30: over 30 minutes (over 1800 s). A case with no estimate is in no numeric bucket. Any other word is a 400 that names the values.

origin
Array<string>
Allowed values: manual ai-generated ai-edited

Only the cases with one of these origins (#1879); repeat the key or separate with commas for OR, together with the other filters. manual: a person wrote it (and every case from before origin existed). ai-generated: made from an approved proposal, or a field accepted from an AI draft, and nobody corrected it. ai-edited: a person corrected the AI text, before approval or after. The same value as origin on each case; matching counts what the filter admits. Any other word is a 400 that names the values.

custom
Array<string>

<fieldId>:<value> (#1766, ADR 0048): only the cases whose custom field holds the value — the text of a string field, the option id of a list (a multiple list matches when it holds it), the number of a number field. Repeat for one field to OR its values; different fields are AND-ed. A field or a value nobody holds matches nothing; a pair without a field or a value is a 400 that names the form.

Your test cases

Media type application/json
object
testCases
required
Array
One of:

A stored test case — the metadata plus the body of its type.

object
schemaVersion
required

Accepted: 1 or 2. Responses are always 2 — a v1 record is upgraded on read.

integer
Allowed values: 1 2
id
required
string format: uuid
projectId
required
string
title
required

Carries its @tag tail — the one source of tags (ADR 0032). The dashboard shows the clean title and the tags as chips.

string
state
required

The case’s place in its lifecycle. in_review is a case a proposal produced and nobody has confirmed yet; detached is one whose source stopped being reported. Who may move it where depends on whether the caller is a session or a token.

string
Allowed values: draft in_review active detached deprecated
status
required

Derived from state for clients on v1, never stored. in_review answers as draft and detached as active — v1 has no word for either.

string
Allowed values: draft active archived
stateReason

Why the case last moved.

string
defects

Only when reading ONE case (#1064): its defect links, oldest first, at most 20; [] for a case without any. The list carries a count per case instead (defectCounts).

Array<object>

A defect (#1064): the address of a ticket somebody already filed, kept beside a case or a failed result. https only, at most 2000 characters; the label is plain text, at most 200, and is shown as text, never as markup. The author is a userId; the dashboard names them from GET /v1/members. No type: Bugs (#1065) will read the same links as external tickets.

object
id
required
string format: uuid
url
required
string
<= 2000 characters /^[Hh][Tt][Tt][Pp][Ss]:///
label
required
string
nullable <= 200 characters
authorId
required
string
createdAt
required
string format: date-time
allowedStates

Only when reading ONE case: the states the caller may move it to from here, per the lifecycle table and who the caller is - a session is a person, a bearer token is automation. A screen renders exactly these as actions and never lists moves itself. The current state is not included.

Array<string>
Allowed values: draft in_review active detached deprecated
stateChangedAt

When it last moved. Absent on a case that has not moved.

string format: date-time
suiteId

The suite this case is filed under, if any. Always a suite, never a folder (AC-03).

string
priority
required

Five levels in rank order — low < normal < important < high < critical. Metadata: changing it does not bump the text version.

string
Allowed values: low normal important high critical
estimate

The time the case takes by hand (#1881), as 1h 30m, 5m or 45s — the spelling a write takes back. Absent while nobody estimated it (stored as NULL, which is not 0). Metadata: changing it does not bump the text version.

string
estimateSeconds

estimate in whole seconds, for a report that adds them up. Absent with estimate; send estimate to change it.

integer
<= 3600000
position
required

Order among the cases of its suite (or the unfiled ones) — dense, 0-based, renumbered on every write that places a case (AC-05).

integer
isFlaky
required

An assertion about the case (#1246): this test is unstable by nature. Diagnostics — a person or a machine may set it. Always present, false until set. Metadata: changing it does not bump the text version. ?flaky=1 (or 0) on the list filters on it.

boolean
custom

Field id → value, as stored on the entity. A string field takes text (1–1 000 characters), a number field a number, a list field the id of one option — or, when multiple, an array of option ids without repeats (up to 50), kept in the order written. At most 16 384 bytes of JSON.

object
key
additional properties
One of:
string
isMuted
required

A person’s decision about the case (#1246): it still runs and its results are recorded, and a failure is not to count toward the run (#1247). Not the same as isFlaky — a flaky case need not be muted, and a muted one need not be flaky. Always present, false until set; a parameterized case is one case, so muting it mutes every row of its parameters. A mute may come from a token; the audit names who — a person, or the token (a runner’s is_muted is an annotation in the code, written and reviewed like test.skip). Every set and clear is an entry in the case’s audit feed. ?muted=1 (or 0) on the list filters on it.

boolean
tags
required

Read from the title by the server, each once, in first-seen order. Derived: sent back, never taken on the way in as a field of its own — tags on a write rewrites the title.

Array<string>
description

Free text beside the steps. Part of what a result was produced against, so a change bumps the text version (AC-09). Absent when never set. 16 384 bytes of UTF-8 at most.

string
precondition

What must hold before the steps. Same rules as description.

string
examples

The parameters table (D7). Content like the two texts: a change bumps the version.

object
params
required
Array<string>
>= 1 items <= 16 items
rows
required
Array<Array<string>>
>= 1 items <= 100 items
externalKeys

The identifiers this case has elsewhere (D2) — attached on every read; absent when it has none.

Array<object>
<= 32 items

An identifier another tool already uses for this test, so a report can find its case without anyone typing a Plune id. The order of kind is load-bearing: it is the ranking that decides which key wins when several match different cases.

object
kind
required
string
Allowed values: playwright-id path-title allure-history cairn-stable eval-id qase testrail testomat zephyr
value
required
string
<= 1024 characters
evalId

The id of this case’s eval in plune.yaml. An ingested run attaches its results to the case carrying the same id (ADR 0010); absent for cases the CLI does not run.

string
codeSnapshot

Only when reading ONE case: the source text of the one test() call an automated case references — the call itself, header and body, exactly as written in the file. Absent on a case that has never had one; present-and-unchanged when the file most recently failed to parse (the last known snapshot, not empty). 16 384 bytes of UTF-8 at most, the same ceiling description/precondition already carry.

string
codeSnapshotHash

SHA-256 hex digest of codeSnapshot, recomputed by the server on every write that touches it (ADR-0005) — never accepted from a client. Present whenever codeSnapshot is, and on the list too even though the text itself is not: a re-importer hashes its own freshly parsed text the same way and compares against this to decide changed vs unchanged without a second request per case.

string
createdBy

Who wrote it (B8-team T3): the userId of the caller of POST /v1/test-cases or of a duplicate. Set by the server, never taken from a body, never changed by a PATCH. Absent on a case from before names were kept and on one the review queue created. Resolve it through GET /v1/members.

string
assigneeId

Who is to run it (B8-team T4): the userId of a member of the project. Set by PATCH {assigneeId} or the bulk assign action, never on create; null on a PATCH takes it away; leaving the project takes it away too. Absent means unassigned. GET /v1/test-cases?assignee=me|none|<userId> filters on it.

string
origin

Where the text of the case came from (#1879), derived on read - no column, so every case has one, the ones from before included. manual: a person wrote it. ai-generated: created from an approved proposal in the review queue, or a field accepted from an AI draft, and not corrected since. ai-edited: a person corrected AI-written content - the reviewer before approving, or anyone after (the version moved), or the text of an accepted draft. Read-only; on the list and on the single read. GET /v1/test-cases?origin= filters on it.

string
Allowed values: manual ai-generated ai-edited
originDetail

The facts behind origin, on the single read only (#1879) - for a tooltip. Only what is already stored; ids of people, never addresses.

object
proposedBy

The userId that put the proposal in the review queue.

string
source

What the supplier said the proposal was derived from.

object
kind
required
string
Allowed values: url openapi-operation requirement pasted-text
value
required
string
>= 1 characters <= 100 characters
editedFields
required

Fields a person changed on AI text: title, steps, expected (the reviewer, #1878), description, precondition (after an accepted draft).

Array<string>
editedAfterCreation
required

A case made from a proposal whose content (title, texts, steps, parameters) has changed since.

boolean
approvedBy

The userId of the latest approval of the standing revision; absent when the policy merged it by itself.

string
approvedAt
string format: date-time
provenance

Where the case came from, present only on one an approved proposal created (B-1). Read through that proposal rather than stored on the case, so it answers for every case ever created this way and can never disagree with the queue entry beside it. Absent on a case a person wrote — and absent, too, when the proposal named neither field: both mean nothing was reported.

object
source
object
kind
required
string
Allowed values: url openapi-operation requirement pasted-text
value
required
string
>= 1 characters <= 100 characters
technique

The test-design technique the supplier named, as they named it.

string
<= 32 characters
textOrigin

Where a field’s text came from, once it was accepted from an AI draft (automated-case-description-draft, AC-17): per field, the model, the date of the draft, who asked, who accepted and when, and — if a person edited the text afterwards — who and when. A field has an entry only while its text is the one a draft produced: the next accepted draft replaces it and clearing the field drops it. The key is absent when no field was ever accepted from a draft. Written only by the case store’s single write path — never taken from a client body, on a PATCH or on Markdown import alike.

object
description

One field’s provenance (AC-17). The same facts the dashboard shows under the field; the hash the server compares the code against is never part of it.

object
model
required

The model that drafted this text, as recorded on the call at the time — not the project’s current model: a later change must not relabel old text.

string
provider

#829: the provider that made the draft, as recorded on the call. Absent on text accepted before the fallback existed.

string
Allowed values: anthropic openrouter custom
viaFallback

#829: true when the fallback provider made the draft. Absent (not false) on text accepted before the fallback existed.

boolean
draftedAt
required

When the draft that produced this text was requested — “the date of the draft”.

string format: date-time
requestedBy
required

userId of the member who asked for the draft — resolve through GET /v1/members like TestCaseMeta.createdBy.

string
acceptedBy
required

userId of the member who accepted it into the case.

string
acceptedAt
required
string format: date-time
editedBy

Present when a person edited this field after accepting it (AC-17); absent otherwise.

string
editedAt

When editedBy last edited it. Present exactly when editedBy is.

string format: date-time
codeChangedSinceAccepted
required

Whether the case’s code snapshot changed, beyond whitespace, since this text was drafted (AC-16, AC-04b) — read again every time the case is read, not frozen when the text was accepted.

boolean
precondition

One field’s provenance (AC-17). The same facts the dashboard shows under the field; the hash the server compares the code against is never part of it.

object
model
required

The model that drafted this text, as recorded on the call at the time — not the project’s current model: a later change must not relabel old text.

string
provider

#829: the provider that made the draft, as recorded on the call. Absent on text accepted before the fallback existed.

string
Allowed values: anthropic openrouter custom
viaFallback

#829: true when the fallback provider made the draft. Absent (not false) on text accepted before the fallback existed.

boolean
draftedAt
required

When the draft that produced this text was requested — “the date of the draft”.

string format: date-time
requestedBy
required

userId of the member who asked for the draft — resolve through GET /v1/members like TestCaseMeta.createdBy.

string
acceptedBy
required

userId of the member who accepted it into the case.

string
acceptedAt
required
string format: date-time
editedBy

Present when a person edited this field after accepting it (AC-17); absent otherwise.

string
editedAt

When editedBy last edited it. Present exactly when editedBy is.

string format: date-time
codeChangedSinceAccepted
required

Whether the case’s code snapshot changed, beyond whitespace, since this text was drafted (AC-16, AC-04b) — read again every time the case is read, not frozen when the text was accepted.

boolean
createdAt
required
string format: date-time
updatedAt
required
string format: date-time
type
required
string
Allowed values: manual
steps
required
Array<object>
object
action
required
string
expected
required
string
matching
required

How many cases the filter admits, across all pages — equal to total without a filter (#1391).

integer
nextCursor
required

Pass as cursor for the next page; null on the last page (#1391).

string
nullable
defectCounts

How many defect links each case holds (#1064), for the cases that hold any - the rest are absent. For the mark in the tree and the count in the drawer; counted over the whole project, whatever the filter. Sent with the first page only (no cursor); a later page leaves it out.

object
key
additional properties
integer
>= 1 <= 20
total
required

Every case you own, whatever the filter — the «of N» a narrowed list is read against.

integer
stability
required

Stability per case id, for the cases ON THIS PAGE that have run history — the rest are absent. Kept beside the list rather than inside each record: a TestCase is what was authored, and a score derived from runs is not part of it.

object
key
additional properties

How stable the case has been, measured as transitions INTO failure ÷ runs — not a failure rate. The two are different diseases: an eval that fails every time is broken (0 transitions, stable), one that alternates is a coin toss. Read runs/failures alongside level, or a permanently broken case looks fine.

object
score
required

Transitions into failure ÷ runs. 0 for a history of ≤ 1.

number
level
required
string
Allowed values: stable flaky critical
runs
required

Runs the score is based on — a score over 2 runs means little.

integer
failures
required
integer
Example
{
"testCases": [
{
"schemaVersion": 1,
"title": "Checkout — empty cart @smoke @checkout",
"state": "draft",
"status": "draft",
"defects": [
{
"id": "6f1c2b3a-0000-4000-8000-0000000000a7",
"url": "https://tracker.example.com/browse/PLN-482",
"label": "Coupon is not applied on an empty cart",
"authorId": "6f1c2b3a-0000-4000-8000-0000000000a2"
}
],
"allowedStates": [
"draft"
],
"priority": "low",
"estimate": "1h 30m",
"estimateSeconds": 5400,
"custom": {
"6f1c2b3a-0000-4000-8000-0000000000f4": [
"opt_chrome0001",
"opt_firefox001"
]
},
"tags": [
"smoke",
"checkout"
],
"examples": {
"params": [
"Amount",
"Outcome"
],
"rows": [
[
"10.00",
"pending"
],
[
"0.00",
"refused"
]
]
},
"externalKeys": [
{
"kind": "playwright-id"
}
],
"origin": "manual",
"originDetail": {
"source": {
"kind": "url"
}
},
"provenance": {
"source": {
"kind": "url"
}
},
"textOrigin": {
"description": {
"provider": "anthropic"
},
"precondition": {
"provider": "anthropic"
}
},
"type": "manual"
}
],
"stability": {
"additionalProperty": {
"level": "stable"
}
}
}
Server-Timing
string

Sends a Server-Timing header (W3C): where the server spent the time, in milliseconds to a tenth — DevTools lists it under Network → Timing. auth is the token or session and the project, counted from the start of the request; rest is the page of cases and their counts; stability is the badges of that page — the read of its cases’ histories and their scoring, asked once the page is known. total is the whole answer, before it is sent. The names are fixed and carry nothing of yours.

Example
auth;dur=3.9, stability;dur=5.3, rest;dur=7.8, total;dur=13.7

A priority, type or state that does not exist, an estimate that is not one of the four buckets, an origin that is not one of the three, or a flaky/muted value other than 1, true, 0 or false (the message names the allowed ones, AC-12), or a cursor that is not one

Media type application/json
object
error
required
string
Example
{
"error": "unknown priority \"urgent\" — priorities are low, normal, important, high, critical"
}

No / invalid token or session