Create a test case
const url = 'https://beta-api.plune.ai/v1/test-cases';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"title":"Login — happy path @smoke","suiteId":"example","evalId":"example","priority":"low","tags":["smoke","checkout"],"description":"example","precondition":"example","position":1,"type":"manual","steps":[{"action":"example","expected":"example"}]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://beta-api.plune.ai/v1/test-cases \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "title": "Login — happy path @smoke", "suiteId": "example", "evalId": "example", "priority": "low", "tags": [ "smoke", "checkout" ], "description": "example", "precondition": "example", "position": 1, "type": "manual", "steps": [ { "action": "example", "expected": "example" } ] }'Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”Title (+ optional suite) plus the authoring body of the chosen type.
object
May carry @tags. What is left once they are removed must not be empty.
Example
Login — happy path @smokeMust be an existing SUITE in your project — a folder is refused with 400 (AC-03).
The eval id in plune.yaml that runs this case.
Rewrites the title’s tail: the clean title, then @a @b, each once. Every entry must be a tag on its own.
Example
[ "smoke", "checkout"]Up to 16 384 bytes of UTF-8.
Up to 16 384 bytes of UTF-8.
The slot among the cases of the suite; omitted = after the last one.
object
Title (+ optional suite) plus the authoring body of the chosen type.
object
May carry @tags. What is left once they are removed must not be empty.
Example
Login — happy path @smokeMust be an existing SUITE in your project — a folder is refused with 400 (AC-03).
The eval id in plune.yaml that runs this case.
Rewrites the title’s tail: the clean title, then @a @b, each once. Every entry must be a tag on its own.
Example
[ "smoke", "checkout"]Up to 16 384 bytes of UTF-8.
Up to 16 384 bytes of UTF-8.
The slot among the cases of the suite; omitted = after the last one.
Example
e2e/login.spec.ts#happy-pathTitle (+ optional suite) plus the authoring body of the chosen type.
object
May carry @tags. What is left once they are removed must not be empty.
Example
Login — happy path @smokeMust be an existing SUITE in your project — a folder is refused with 400 (AC-03).
The eval id in plune.yaml that runs this case.
Rewrites the title’s tail: the clean title, then @a @b, each once. Every entry must be a tag on its own.
Example
[ "smoke", "checkout"]Up to 16 384 bytes of UTF-8.
Up to 16 384 bytes of UTF-8.
The slot among the cases of the suite; omitted = after the last one.
Stored with the case and returned unchanged. It links to nothing: golden sets were removed in ADR 0013, so this is a label the platform keeps, not a reference it resolves.
One assertion: a type plus whatever that type needs (ADR 0012). The remaining fields depend on type (e.g. values for contains-all, criteria for llm-judge).
object
Example
llm-judgeResponses
Section titled “ Responses ”Created — for an automated case without a suite, already filed under the suite its specRef names
A stored test case — the metadata plus the body of its type.
object
Accepted: 1 or 2. Responses are always 2 — a v1 record is upgraded on read.
Carries its @tag tail — the one source of tags (ADR 0032). The dashboard shows the clean title and the tags as chips.
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.
Derived from state for clients on v1, never stored. in_review answers as draft and detached as active — v1 has no word for either.
Why the case last moved.
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.
When it last moved. Absent on a case that has not moved.
The suite this case is filed under, if any. Always a suite, never a folder (AC-03).
Five levels in rank order — low < normal < important < high < critical. Metadata: changing it does not bump the text version.
Order among the cases of its suite (or the unfiled ones) — dense, 0-based, renumbered on every write that places a case (AC-05).
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.
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.
What must hold before the steps. Same rules as description.
The identifiers this case has elsewhere (D2) — attached on every read; absent when it has none.
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
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.
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
object
The test-design technique the supplier named, as they named it.
object
A stored test case — the metadata plus the body of its type.
object
Accepted: 1 or 2. Responses are always 2 — a v1 record is upgraded on read.
Carries its @tag tail — the one source of tags (ADR 0032). The dashboard shows the clean title and the tags as chips.
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.
Derived from state for clients on v1, never stored. in_review answers as draft and detached as active — v1 has no word for either.
Why the case last moved.
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.
When it last moved. Absent on a case that has not moved.
The suite this case is filed under, if any. Always a suite, never a folder (AC-03).
Five levels in rank order — low < normal < important < high < critical. Metadata: changing it does not bump the text version.
Order among the cases of its suite (or the unfiled ones) — dense, 0-based, renumbered on every write that places a case (AC-05).
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.
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.
What must hold before the steps. Same rules as description.
The identifiers this case has elsewhere (D2) — attached on every read; absent when it has none.
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
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.
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
object
The test-design technique the supplier named, as they named it.
A stored test case — the metadata plus the body of its type.
object
Accepted: 1 or 2. Responses are always 2 — a v1 record is upgraded on read.
Carries its @tag tail — the one source of tags (ADR 0032). The dashboard shows the clean title and the tags as chips.
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.
Derived from state for clients on v1, never stored. in_review answers as draft and detached as active — v1 has no word for either.
Why the case last moved.
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.
When it last moved. Absent on a case that has not moved.
The suite this case is filed under, if any. Always a suite, never a folder (AC-03).
Five levels in rank order — low < normal < important < high < critical. Metadata: changing it does not bump the text version.
Order among the cases of its suite (or the unfiled ones) — dense, 0-based, renumbered on every write that places a case (AC-05).
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.
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.
What must hold before the steps. Same rules as description.
The identifiers this case has elsewhere (D2) — attached on every read; absent when it has none.
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
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.
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
object
The test-design technique the supplier named, as they named it.
Stored with the case and returned unchanged. It links to nothing: golden sets were removed in ADR 0013, so this is a label the platform keeps, not a reference it resolves.
One assertion: a type plus whatever that type needs (ADR 0012). The remaining fields depend on type (e.g. values for contains-all, criteria for llm-judge).
object
Example
{ "schemaVersion": 1, "title": "Checkout — empty cart @smoke @checkout", "state": "draft", "status": "draft", "allowedStates": [ "draft" ], "priority": "low", "tags": [ "smoke", "checkout" ], "externalKeys": [ { "kind": "playwright-id" } ], "provenance": { "source": { "kind": "url" } }, "type": "manual"}Invalid body, a suite that is missing / not yours, a suite that is a folder, a title made of tags only, an invalid tag, or an unknown priority — each refusal names what it refused
object
Examples
AC-03 — the target is a folder
{ "error": "suite '6f1c2b3a-0000-4000-8000-0000000000f1' is a folder — cases go in suites"}AC-06 — nothing is left of the title once the tags are removed
{ "error": "validation failed — title: a title cannot consist of tags only"}AC-06 — the refusal names the tag
{ "error": "validation failed — tags.1: invalid tag 'smoke test' — a tag is 1 to 119 characters from \\w = - _ ( ) . : &"}AC-06 — the five levels are listed
{ "error": "validation failed — priority: must be one of low, normal, important, high, critical"}No / invalid token or session
Rate-limited: either the per-route throttle or a tenant quota. Retry-After carries the seconds until the window rolls over.
object
Example generated
{ "error": "example"}Headers
Section titled “Headers ”Seconds until the window rolls over