Start a run: proposals of test cases from pasted text
const url = 'https://beta-api.plune.ai/v1/projects/example/case-generations';const options = { method: 'POST', headers: { cookie: 'plune_session=<plune_session>', 'Content-Type': 'application/json' }, body: '{"input":{"kind":"pasted-text","title":"Password reset","text":"As a shopper I want to reset my password from the sign in page, so that I can get back into my account when I forget it.\nAcceptance criteria:\n- the reset link is sent to the e-mail of the account\n- the link expires after one hour\n- a used link cannot be used again"},"maxCases":6,"language":"en"}'};
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/projects/example/case-generations \ --header 'Content-Type: application/json' \ --cookie plune_session=<plune_session> \ --data '{ "input": { "kind": "pasted-text", "title": "Password reset", "text": "As a shopper I want to reset my password from the sign in page, so that I can get back into my account when I forget it.\nAcceptance criteria:\n- the reset link is sent to the e-mail of the account\n- the link expires after one hour\n- a used link cannot be used again" }, "maxCases": 6, "language": "en" }'The text (a story, its acceptance criteria) is masked for secrets and put on the queue; the worker sends it to the project’s own AI provider and writes what comes back as proposals in the review queue, each pending, execution: manual, with source {kind: pasted-text}, origin {generationId, kind: ticket, target}, the technique and the run’s cost. Nothing is created in the case tree: a case appears when a reviewer approves a proposal. The text is not stored — the run keeps a hash and counts. Read the run with GET …/case-generations/{generationId}. Answers 202 with the run; the same text within 10 minutes answers 202 with the run it already made, at no new cost — before the monthly limit, the queue’s ceiling and the run limits are asked. Refusals, in order: a reader (403), a token (403 — a person in a session only), the body (400), the ticket mode (424), the text (422), the project’s AI (503/409), the monthly AI limit (429 ai_budget_spent, Retry-After until the 1st, UTC), a full review queue (429 with a quota body, as every quota refusal), proposals or cases from the same text without confirmDuplicates (409 duplicate_source, listed), and the run limits: 3 an hour a member, 20 a day a project, one at a time a project (429 with Retry-After).
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”An id from GET /v1/projects
Request Body required
Section titled “Request Body required ”object
object
The story and its acceptance criteria. Over 12000 characters is a 422 (text_too_long), under 20 words a 422 (ticket_too_empty).
What the text is called; it becomes the source the proposals name.
A Jira ticket by its key. Not available yet: answered 424 jira_not_connected (the ticket mode comes with the Jira connection).
object
The most proposals the run may produce.
The language of the cases; the project’s AI language when left out.
Proposals or cases from the same text already exist (409 duplicate_source): true generates anyway.
Examples
Six cases from a story
{ "input": { "kind": "pasted-text", "title": "Password reset", "text": "As a shopper I want to reset my password from the sign in page, so that I can get back into my account when I forget it.\nAcceptance criteria:\n- the reset link is sent to the e-mail of the account\n- the link expires after one hour\n- a used link cannot be used again" }, "maxCases": 6, "language": "en"}The same text again, duplicates confirmed
{ "input": { "kind": "pasted-text", "title": "Password reset", "text": "As a shopper I want to reset my password from the sign in page, so that I can get back into my account when I forget it.\nAcceptance criteria:\n- the reset link is sent to the e-mail of the account\n- the link expires after one hour\n- a used link cannot be used again" }, "maxCases": 6, "language": "en", "confirmDuplicates": true}Responses
Section titled “ Responses ”The run, queued — or the one the same text made within 10 minutes
One run (ADR 0067 point 4). It never holds the text: what went to the model is counted (fetched), masked secrets are counted (redactions), and the text itself lives only in the proposals the run wrote.
object
Characters of each field sent to the model, after masking.
object
How many places were masked as a secret before anything was sent.
Proposals put in the review queue, each pending.
Proposals left out: invalid broke a bound of the proposal (the field named), over_limit came past maxCases.
object
Tokens null = the provider reported none; costUsd null beside tokens = the model’s price is unknown — never 0. Counted in the project’s monthly AI limit.
object
Where the run was sent, once it started.
Only on failed.
Example
{ "id": "6f1c2b3a-0000-4000-8000-0000000000aa", "state": "queued", "inputKind": "pasted-text", "maxCases": 6, "language": "en", "fetched": { "title": 14, "text": 214 }, "redactions": 0, "produced": 0, "skipped": [], "cost": { "inputTokens": null, "outputTokens": null, "costUsd": null }, "requestedBy": "6f1c2b3a-0000-4000-8000-0000000000a2", "createdAt": "2026-10-25T09:00:00.000Z", "startedAt": null, "finishedAt": null}Invalid JSON or body
object
Example generated
{ "error": "example"}No / invalid session
You are in the project as a reader, or a token (a person in a session only)
No such project among yours
object
Example generated
{ "error": "example"}duplicate_source (with the list), ai_not_connected, ai_disabled, or key_rejected (the saved key cannot be read)
object
text_too_long: the most characters a run takes.
ticket_too_empty: the fewest words a run takes.
What the project already holds from the same text: the proposals earlier runs of it put in the queue (any state but approved), and the cases the approved ones became.
object
object
object
ai_budget_spent: the owner’s monthly limit.
ai_budget_spent: what the month has spent, known-price calls and runs only.
ai_budget_spent: the day the limit frees (the 1st, UTC).
Example
{ "code": "text_too_long"}text_too_long / ticket_too_empty
object
text_too_long: the most characters a run takes.
ticket_too_empty: the fewest words a run takes.
What the project already holds from the same text: the proposals earlier runs of it put in the queue (any state but approved), and the cases the approved ones became.
object
object
object
ai_budget_spent: the owner’s monthly limit.
ai_budget_spent: what the month has spent, known-price calls and runs only.
ai_budget_spent: the day the limit frees (the 1st, UTC).
Example
{ "code": "text_too_long"}jira_not_connected: the ticket mode is not available
object
text_too_long: the most characters a run takes.
ticket_too_empty: the fewest words a run takes.
What the project already holds from the same text: the proposals earlier runs of it put in the queue (any state but approved), and the cases the approved ones became.
object
object
object
ai_budget_spent: the owner’s monthly limit.
ai_budget_spent: what the month has spent, known-price calls and runs only.
ai_budget_spent: the day the limit frees (the 1st, UTC).
Example
{ "code": "text_too_long"}The monthly AI limit (ai_budget_spent) or a run limit (member_limit, project_limit, concurrent_limit), each with Retry-After; or the review queue’s ceiling — a quota body (kind: pendingReviewItems) and no Retry-After, since only working the queue down frees it
object
text_too_long: the most characters a run takes.
ticket_too_empty: the fewest words a run takes.
What the project already holds from the same text: the proposals earlier runs of it put in the queue (any state but approved), and the cases the approved ones became.
object
object
object
ai_budget_spent: the owner’s monthly limit.
ai_budget_spent: what the month has spent, known-price calls and runs only.
ai_budget_spent: the day the limit frees (the 1st, UTC).
The body of a 429 that is a quota and not a throttle: the sentence, and the figures behind it.
object
What was reached and the way out.
object
The ceiling. A write that would take used past it is refused with 429.
What the project holds now, counted by the same query that refuses a write.
limit - used, never below 0.
When the oldest counted run ages out of the rolling 24 hours and a slot frees on its own. null for the two quotas only an action frees (delete cases, decide queue items), and for a run quota with no runs in the window.
Example
{ "code": "text_too_long"}Headers
Section titled “Headers ”Seconds until the limit lets the next run through: the run window, or the 1st of next month (UTC) for ai_budget_spent
platform_not_configured: the server has no AI encryption key
object
text_too_long: the most characters a run takes.
ticket_too_empty: the fewest words a run takes.
What the project already holds from the same text: the proposals earlier runs of it put in the queue (any state but approved), and the cases the approved ones became.
object
object
object
ai_budget_spent: the owner’s monthly limit.
ai_budget_spent: what the month has spent, known-price calls and runs only.
ai_budget_spent: the day the limit frees (the 1st, UTC).
Example
{ "code": "text_too_long"}