Skip to content

Accept selected fields of the pending draft, or reject it

PATCH
/v1/test-cases/{id}/draft
curl --request PATCH \
--url https://beta-api.plune.ai/v1/test-cases/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/draft \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "state": "accepted", "fields": { "description": "Opens the checkout page with an empty cart and asserts the empty-state message.", "precondition": "A logged-in user with an empty cart." } }'

Mirrors PATCH /v1/review-items/{id}’s { state } shape. state: accepted takes fields — the final text of description and/or precondition (as the draft read it, or edited); a field left out of fields is not taken and the case keeps what it had (AC-03) — and so is a field sent empty or holding only whitespace: a field the member cleared takes nothing, and an accept with nothing left to take answers 400. Only the member who requested the pending draft may decide it (AC-19); a bearer token gets the same 403 as on POST. The server compares the CURRENT case text against the fingerprint it took when the draft was requested — the client never sends one (ADR-0002): a field someone changed in between answers 409 with the new text, and the draft is neither consumed nor billed again (AC-04). Accepting when the code changed since the request (not just whitespace) still succeeds; the accepted field is marked as drafted from a previous version of the code (AC-04b, AC-16) rather than refused. A draft is decided once: of two decisions arriving together — accept and reject from two tabs, or a double click on accept — one decides and the other answers 409, and the case carries only what the one that decided wrote.

id
required
string format: uuid
Media type application/json

state: accepted requires fields with at least one of description/precondition — the final text to store, exactly as the member wants it kept (draft text verbatim, or edited). state: rejected takes no fields.

object
state
required
string
Allowed values: accepted rejected
fields
object
description
string
<= 16384 characters
precondition
string
<= 16384 characters
Examples

AC-02 — take both fields as the draft wrote them

{
"state": "accepted",
"fields": {
"description": "Opens the checkout page with an empty cart and asserts the empty-state message.",
"precondition": "A logged-in user with an empty cart."
}
}

Decided — testCase is the updated case on accepted, null on rejected

Media type application/json
object
call
required

One call record (ADR-0002), as returned to a project member. When mine is false — a pending draft somebody else requested — every field below requestedAt is withheld (AC-19): content, tokens and cost are for the requester alone until a decision is made.

object
id
required
string
state
required
string
Allowed values: in_progress awaiting_decision accepted rejected expired failed aborted
mine
required

Whether the caller is the member who requested this call.

boolean
requestedBy
required

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

string
requestedAt
required
string format: date-time
description

The drafted description, present while there is one to show (mine, and state carries text).

string
nullable
precondition
string
nullable
unseenNote

«Чого чернетка не бачила» — fixtures, hooks, helpers (AC-14). Never a field of the case.

string
nullable
codeChangedSinceRequest

Whether the case’s code snapshot changed, beyond whitespace, since this call was requested (AC-04b).

boolean
inputTokens
integer
nullable
outputTokens
integer
nullable
unitPriceUsd

The average price per token of this call (costUsd ÷ tokens) — input and output tokens are priced apart. null means “ціна невідома” (the model has no price) or that the provider reported no tokens to price; never 0 (SAD §8 rule 6, AC-15).

number
nullable
costUsd

null exactly when unitPriceUsd is null.

number
nullable
durationMs
integer
nullable
failureKind

Present exactly when state is failed.

string
Allowed values: key_rejected provider_rate_limited provider_unavailable invalid_response address_rejected
testCase

The updated case on accepted (unchanged TestCase shape, TestCaseMeta.textOrigin included); null on rejected — the case did not change (AC-03).

object
Examples
{
"call": {
"id": "6f1c2b3a-0000-4000-8000-0000000000c1",
"state": "accepted",
"mine": true,
"requestedBy": "u-member1",
"requestedAt": "2026-09-16T09:12:04.151Z"
},
"testCase": {
"id": "6f1c2b3a-0000-4000-8000-0000000000c1",
"description": "Opens the checkout page with an empty cart and asserts the empty-state message."
}
}

Invalid body — unknown state, fields empty on accepted, or a field over 16384 bytes (AC-18)

Media type application/json
object
error
required
string
Examples
{
"error": "validation failed — fields: at least one of description, precondition is required when accepting"
}

No / invalid token or session

A bearer token asked, or a different member than the one who requested this draft

Media type application/json
object
error
required
string
Examples
{
"error": "requires a human session — a token cannot request or decide a draft"
}

Not found or not yours

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

AC-04 — the draft is not (or no longer) awaiting a decision, OR a field being taken changed on the case after the draft was requested, OR the case kept changing while the draft was being accepted (#1019): someone else saved it again and again, and nothing was written. The second carries current — the fields that changed, with their text as the case holds it now — so the client shows it beside the draft, per AC-04. The third carries current too, with every field being taken as it stands, which may be the very text the draft was requested against: what moved is the case, not necessarily those fields. The draft stays waiting, and accepting it again is the retry.

Media type application/json
object
error
required
string
current

The field(s) that changed, as the case holds them now (AC-04) — present only on the field-conflict branch, absent on “not awaiting a decision”.

object
description
string
precondition
string
Examples

Already decided (a moment ago, from another tab, too), expired, or superseded by a newer request

{
"error": "this draft is no longer awaiting a decision"
}

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