Skip to content

Import requirements from a Markdown file (a dry run by default)

POST
/v1/projects/{id}/requirements/import
curl --request POST \
--url https://beta-api.plune.ai/v1/projects/example/requirements/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "markdown": "# Billing SRS\n\n## 3. Functional requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| REQ-FN-001 | The system **shall** let a user sign in with an e-mail link. | Critical |\n| REQ-FN-002 | The system shall export an invoice as a PDF file. | High |\n", "mode": "table", "table": 0, "keyColumn": "ID", "textColumn": "Requirement", "priorityColumn": "Priority", "priorityMap": { "Critical": "Must-have", "High": "Should-have" } }'

Reads the table (mode: table) or the section (mode: sections) the person picked after .../import/discover, with the columns they mapped, and answers what it does with each row: create, update, unchanged or skip, each with a reason and the line in the file. dryRun is true unless the body says false: a dry run writes nothing. dryRun: false applies the same plan in ONE transaction (a failure leaves nothing behind) and answers the same report with applied: true and the id of each requirement written. A row with a key matches an existing requirement by that key (any case); a row without one matches by exact title (the reason says matched by title) and a new one gets REQ-<n>. A re-import of the same file changes nothing: every row is unchanged. A field is updated only when the file gives a value and it differs; an empty cell leaves the requirement as it is. Tables and sections not picked are listed in notImported, never dropped silently. Checklist marks ([x]) are never read: coverage is counted from results. A bad row (ragged, a key that is invalid or repeated in the file, a status that is not draft, active or done, a text over the description limit) is skipped with its line; the rest goes on. Refusals: more than 2000 requirements or a file over 1048576 bytes is a 413 naming the limit; an unknown column or table is a 400 naming the headers or the count. caseTables (#1624) chooses Given / When / Then tables as manual test cases: unticked by default, so a table not listed there creates nothing and stays in notImported; the cases are written in the same transaction as the requirements, each with its own audit entry, and a project over its case ceiling is a 429 with nothing written. describe (#1856) appends a left-out section or table to the description of a requirement of the same import, under its own heading; the file is not changed. Every entry of notImported carries its lines and nearestAbove, the closest requirement above it, which a client offers as the default. An applied import is ONE audit entry with the counts, never the text. An owner or a member (a reader gets 403); a bearer token only in the project it was minted in.

id
required
string

An id from GET /v1/projects

Media type application/json

The file and the mapping. table mode needs table (or tables, several tables in one run) and a titleColumn or a textColumn; sections mode needs section. A field of the other mode is a 400. Columns are named by their header text (any case, emphasis ignored); an unknown name is a 400 listing the headers. keyPattern is not accepted: no pattern is run on a file.

object
markdown
required

At most 1048576 bytes (1 MiB).

string
dryRun

Default true: nothing is written and the report says what would be. false applies everything in one transaction.

boolean
default: true
mode
required
string
Allowed values: table sections
defaultType

The type of a requirement the file does not type (default feature).

string
Allowed values: user_story feature epic
priorityMap

Value to value, applied exactly (any case). A priority not in the map is kept as written and listed in values.priority.

object
key
additional properties
string
table

Table mode: the index of the table from the discover call. For several tables in one run give tables instead.

integer
tables

Table mode, several tables in one run (one preview, the table named on each row): each entry has table and the column fields of the single-table form (keyColumn, titleColumn or textColumn, descriptionColumn, priorityColumn, categoryColumn, typeColumn, statusColumn, acceptanceCriteriaColumn, categoryMap) and fixedCategory / fixedType. Instead of table and those fields at the top level, never together. priorityMap and defaultType stay shared. The limits (1 MiB, 2000 rows) apply to all the tables together, and a key must be unique across them.

Array<object>
>= 1 items <= 50 items
object
table
required
integer
keyColumn
string
titleColumn
string
textColumn
string
descriptionColumn
string
priorityColumn
string
categoryColumn
string
typeColumn
string
statusColumn
string
acceptanceCriteriaColumn
string
categoryMap
object
key
additional properties
string
fixedCategory
string
Allowed values: functional non-functional other
fixedType
string
Allowed values: user_story feature epic
caseTables

Given / When / Then tables to create MANUAL TEST CASES from (#1624), table or sections mode. Without this nothing is created: a table whose headers include Given, When and Then is only proposed in the report (cases, selected: false) and stays in notImported.tables. Each entry: table (the index from the discover call) and optional idColumn (the scenario id, AC-001), textColumn (default: the When cell), givenColumn / whenColumn / thenColumn (default headers Given, When, Then). A table is either requirements or cases, not both. In table mode table / tables may be left out when only cases are wanted. The rows count against the 2000 of the whole file.

Array<object>
>= 1 items <= 50 items
object
table
required
integer
idColumn
string
textColumn
string
givenColumn
string
whenColumn
string
thenColumn
string
fixedCategory

Table mode: one category for the whole table, used where a row has no category of its own (a column wins).

string
Allowed values: functional non-functional other
fixedType

Table mode: one type for the whole table, used where a row has no type of its own (a column wins).

string
Allowed values: user_story feature epic
keyColumn

Table mode: the column with the external id (REQ-FN-001). It becomes the key, so a second import updates. Without it a row matches by exact title.

string
titleColumn

Table mode: the title. Not together with textColumn.

string
textColumn

Table mode: the text, used as the title (first line, cut at 200 characters at a word boundary) with the whole text as the description when they differ.

string
descriptionColumn

Table mode: the description (Markdown kept; <br> becomes a line break, an escaped pipe a pipe).

string
priorityColumn
string
categoryColumn

Table mode: functional, non-functional or other, or a value categoryMap sends to one of them; any other value is left empty and reported.

string
typeColumn

Table mode: user_story, feature or epic.

string
statusColumn

Table mode: draft, active or done; any other value skips the row. Without it an imported requirement is draft and an existing one keeps its status.

string
acceptanceCriteriaColumn

Table mode: only a column the person names; an “Acceptance signal” or “Observable result” column is never picked by its header.

string
categoryMap

Table mode: value to one of the three categories.

object
key
additional properties
string
section
One of:
string
unit

Sections mode: one requirement per list item or per sub-heading (its body is the description). Default: sub-headings when the section has any, else list items.

string
Allowed values: bullets subheadings
keyPrefix

Sections mode: a list item or sub-heading that starts with this text (any case) carries its key: REQ-12: The system shall....

string
priorityFrom

Sections mode: list items under a sub-heading take its text as their priority.

string
Allowed values: subheading
describe

Both modes (#1856): left-out content as part of a requirement’s description. Each entry: exactly one of section / table (its index from the discover call; it must be listed in notImported of this request) and into (the line of a requirement of this import, as in items). The description is the one the file gives (or none), then for each source in file order ## <heading> and its lines as written: a section its own lines up to the next heading of any level, without its heading line, a table whole (a table without a heading is Table <index + 1>). Front matter is never a source. A description over 16 384 bytes skips the whole requirement with the description would pass 16 KiB with <heading>. An unchanged requirement becomes an update of its description; the same describe again is unchanged. 400: a source that is imported (section 3 is imported, not left out), a heading that holds nothing of its own (section 0 holds nothing of its own) or not in the file, a line that is not a requirement of this import (or one skipped for its own reason), an entry with both or neither of section / table, the same source twice.

Array<object>
>= 1 items <= 50 items
object
section
integer
table
integer
into
required
integer
>= 1
Examples

A table with an id column, as a dry run

{
"markdown": "# Billing SRS\n\n## 3. Functional requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| REQ-FN-001 | The system **shall** let a user sign in with an e-mail link. | Critical |\n| REQ-FN-002 | The system shall export an invoice as a PDF file. | High |\n",
"mode": "table",
"table": 0,
"keyColumn": "ID",
"textColumn": "Requirement",
"priorityColumn": "Priority",
"priorityMap": {
"Critical": "Must-have",
"High": "Should-have"
}
}

The report - the same for a dry run and for an applied import

Media type application/json
object
dryRun
required
boolean
applied
required

true once written; a dry run is false.

boolean
mode
required
string
Allowed values: table sections
summary
required
object
create
required
integer
update
required
integer
unchanged
required
integer
skip
required
integer
items
required

One entry per row, list item or sub-heading read, in file order.

Array<object>
object
line
required

1-based line in the file.

integer
action
required

What the import does (a dry run) or did (applied).

string
Allowed values: create update unchanged skip
key
required

The key in the file, or the key of the requirement a title matched; null for a new requirement without one (it gets REQ-<n>).

string
nullable
title
required
string
reason
required

new, matched by key, matched by title, matched by key: unchanged, or why the row is skipped (names the problem).

string
changes

On an update: the fields that differ (names, never values).

Array<string>
id

Once applied: the id of the requirement created or updated.

string
notes

Things worth a look that did not stop the row (an unknown category or type).

Array<string>
table

Only when tables was sent: the index of the table the row is from.

integer
tableHeading

Only when tables was sent: the heading above that table.

string
nullable
described

Only on an item describe named (#1856): the left-out parts appended to its description, in file order; also on a skip over the 16 KiB limit, to show what did not fit.

Array<object>
object
section
integer
table
integer
heading
required

The heading of the section or the table; null for a table without one.

string
nullable
lines
required

Lines of the file, 1-based, both ends included. A table: its header row to its last row. A section: its heading to the line before the next heading of any level (or the end of the file), its own lines only (#2158).

object
from
required
integer
>= 1
to
required
integer
>= 1
cases

Manual test cases proposed from Given / When / Then tables (#1624); present only when the file has such a table or the request chose one. Title: <id>: <text> (text = textColumn, else the When cell; without an id the text alone), first line, at most 200 characters. The Given cell is the precondition; one step: the When cell as the action, the Then cell as the expected result. Created as drafts, in no suite. A re-import leaves a case alone when the project already has a live one whose title starts with <id>: (or equals the title when the row has no id). create / unchanged of a row with selected: false is what ticking the table would do; only selected: true rows are written.

Array<object>
object
line
required
integer
selected
required

true when the request listed the table in caseTables.

boolean
action
required
string
Allowed values: create unchanged skip
key
required

The id column value, or null.

string
nullable
title
required
string
reason
required

new, matched by id: left as it is, matched by title: left as it is, template placeholder, or why the row is skipped.

string
id

Once applied: the id of the case created.

string
table
required
integer
tableHeading
required
string
nullable
caseSummary

Counts of the SELECTED case rows only.

object
create
integer
unchanged
integer
skip
integer
casesTruncated

More unselected proposals were found than the 2000 listed.

boolean
emptyTemplate

Present when every row read is a template placeholder ([Behavior]): the file is a blank template. Such rows are skipped with the reason template placeholder.

boolean
message

Present with emptyTemplate: this file is an empty template: ....

string
values
required

The distinct values read from the priority and category columns, with what the map says of each: show them for editing.

object
priority
required
Array<object>
object
value
required
string
count
required
integer
mappedTo
required

What the map sends it to; null - no entry (a priority is then kept as written).

string
nullable
category
required
Array<object>
object
value
required
string
count
required
integer
mappedTo
required

What the map sends it to; null - no entry (a priority is then kept as written).

string
nullable
notImported
required

What the import did not read, reported and never silently dropped (at most 200 tables and 200 sections are listed).

object
frontmatterKeys
required
Array<string>
tables
required
Array<object>
object
index
required
integer
heading
required
string
nullable
headers
required
Array<string>
rowCount
required
integer
lines
required

Lines of the file, 1-based, both ends included. A table: its header row to its last row. A section: its heading to the line before the next heading of any level (or the end of the file), its own lines only (#2158).

object
from
required
integer
>= 1
to
required
integer
>= 1
nearestAbove
required
object
line
required
integer
key
required
string
nullable
title
required
string
sections
required
Array<object>
object
index
required
integer
heading
required
string
level
required
integer
bulletCount
required
integer
lines
required

Lines of the file, 1-based, both ends included. A table: its header row to its last row. A section: its heading to the line before the next heading of any level (or the end of the file), its own lines only (#2158).

object
from
required
integer
>= 1
to
required
integer
>= 1
nearestAbove
required
object
line
required
integer
key
required
string
nullable
title
required
string
Example
{
"dryRun": true,
"applied": false,
"mode": "table",
"summary": {
"create": 1,
"update": 0,
"unchanged": 0,
"skip": 1
},
"items": [
{
"line": 7,
"action": "create",
"key": "REQ-FN-001",
"title": "The system **shall** let a user sign in with an e-mail link.",
"reason": "new"
},
{
"line": 8,
"action": "skip",
"key": "REQ-FN-002",
"title": "The system shall export an invoice as a PDF file.",
"reason": "status 'maybe' is not one of draft, active, done"
}
],
"values": {
"priority": [
{
"value": "Critical",
"count": 1,
"mappedTo": "Must-have"
},
{
"value": "High",
"count": 1,
"mappedTo": "Should-have"
}
],
"category": []
},
"notImported": {
"frontmatterKeys": [],
"tables": [],
"sections": [
{
"index": 1,
"heading": "3. Functional requirements",
"level": 2,
"bulletCount": 0,
"lines": {
"from": 3,
"to": 8
},
"nearestAbove": null
}
]
}
}

Invalid JSON, mapping or keyPattern; an unknown column (the message lists the headers), table or section; a describe source that is not left out, or a line that is not a requirement of this import

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

No / invalid token or session

You are in the project as a reader

No such project among yours (or a token of another project)

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

A key was taken by another change while the import ran; nothing was written

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

The markdown is over 1 MiB, or the table or section has more than 2000 requirements

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

Rate-limited: the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442; the body names the plan, #2092). 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