Skip to content

Queue a result for human review

POST
/v1/review-items
curl --request POST \
--url https://beta-api.plune.ai/v1/review-items \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "resultId": "6f1c2b3a-0000-4000-8000-0000000000e1", "note": "Passed only on the retry — a flaky selector or a real race?" }'

A person queues a disputed result here. The Cairn ingest also queues entries, but they are PROPOSED CASES rather than results (#10, ADR-CI-03) and arrive through /v1/ingest/cairn. One item per result.

Media type application/json
object
resultId
required

Must be a result in your project.

string format: uuid
note

Why this one is worth a look.

string
Example
{
"resultId": "6f1c2b3a-0000-4000-8000-0000000000e1",
"note": "Passed only on the retry — a flaky selector or a real race?"
}

Queued, pending

Media type application/json

One entry of the review queue. Always starts pending; a reviewer moves it on.

object
schemaVersion
required
integer
Allowed values: 1
id
required
string format: uuid
resultId

Present on an entry disputing a RESULT. Exactly one of resultId, proposedCase and discoveredTest is present: an entry with two asks two questions at once, and one with none asks nothing.

string format: uuid
proposedCase

Present on an entry proposing a new case. Mutually exclusive with the other two.

object
stableId
required

Content-derived identity; the dedup key.

string
title
required
string
<= 300 characters
execution
required
string
Allowed values: auto manual
steps
required
Array<string>
>= 1 items <= 20 items
expected
required
string
<= 2000 characters
origin
required
object
runId
required
string
mode
required
string
Allowed values: explore design api
target
required
string
technique
string
<= 32 characters
priority
string
source
object
kind
required
string
Allowed values: url openapi-operation requirement
value
required
string
>= 1 characters <= 100 characters
rationale
string
>= 1 characters <= 400 characters
runCost
object
tokens
required
integer
cases
required
integer
verdict

Absent means never executed — NOT “no result yet”.

string
Allowed values: passed failed
externalKeys

What a match resolves through. Absent is a normal proposal, not a degraded one: an agent that names no key gets an entry with no comparison, which is exactly what a genuinely new case gets.

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
value
required
string
<= 1024 characters
discoveredTest

Present on an entry reporting a test the caller RUNS and this project has no case for. Mutually exclusive with the other two.

object
keys
required

The identity. Order does not matter — the server ranks them and the highest-ranked kind is what deduplication reads, so two adapters sending the same keys in a different order do not produce two entries about one test.

Array<object>
>= 1 items <= 8 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
value
required
string
<= 1024 characters
title
required
string
<= 300 characters
source
required

The reporter that found it, e.g. playwright.

string
<= 32 characters
specRef
required

Where the test lives, e.g. e2e/checkout.spec.ts:12. Required while everything descriptive is optional: an approved discovery becomes an automated case, and that shape needs a spec to point at.

string
<= 400 characters
rawStatus

What it did on this run, in the reporter’ own word.

string
<= 64 characters
platformRunId

The run that turned it up, once stored.

string
state
required
string
Allowed values: pending approved rejected
rejectionCategory

Present on every item rejected after R4, and absent forever on rejections stored before it — those are never migrated and must keep reading without error. An item that is pending or approved usually has none, but MAY still carry one: a rejection returned to review keeps the pair in the row as a draft, and the next move to rejected must supply it in the request anyway. Read it as “meaningful only while state is rejected”, not as “absent otherwise”.

string
Allowed values: duplicate incorrect out-of-scope low-value not-testable other
note
string
createdAt
required
string format: date-time
updatedAt
required
string format: date-time
Example
{
"schemaVersion": 1,
"id": "6f1c2b3a-0000-4000-8000-0000000000b1",
"resultId": "6f1c2b3a-0000-4000-8000-0000000000e1",
"state": "pending",
"note": "Passed only on the retry — a flaky selector or a real race?",
"createdAt": "2026-09-16T09:12:04.151Z",
"updatedAt": "2026-09-16T09:12:04.151Z"
}

Missing resultId or an invalid body

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

No / invalid token or session

Result not found or not yours

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

Already queued — the body carries the existing item id

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

Rate-limited: either the per-route throttle or a tenant quota. Retry-After carries the seconds until the window rolls over.

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}
Retry-After
integer

Seconds until the window rolls over