Skip to content

Define a custom field

POST
/v1/custom-fields
curl --request POST \
--url https://beta-api.plune.ai/v1/custom-fields \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "entity": "case", "name": "Browser", "type": "list", "multiple": true, "options": [ { "label": "Chrome" }, { "label": "Firefox" } ], "showInList": true }'

The project owner’s. Needs no migration of the cases that exist: a case without a value just does not have one. At most 50 live fields per entity (429 after that; archive one), a name unique per entity (409). Only entity: case for now (400 for the others).

Media type application/json
object
role

A configuration group (#1251): only with entity: run and type: list, takes several values (multiple is implied), never required, no defaultValue — each a 400 otherwise.

string
Allowed values: configuration
entity
required
string
Allowed values: case suite run plan step
name
required
string
>= 1 characters <= 100 characters
type
required
string
Allowed values: string number list
multiple
boolean
options

A list field needs at least one; no other type takes any. The server gives each an id.

Array<object>
<= 100 items
object
label
required
string
>= 1 characters <= 200 characters
archived

#1251: true makes the option archived from the start (the server sets archivedAt).

boolean
required
boolean
defaultValue
One of:
string
showInList
boolean
showInFilter
boolean
Example
{
"entity": "case",
"name": "Browser",
"type": "list",
"multiple": true,
"options": [
{
"label": "Chrome"
},
{
"label": "Firefox"
}
],
"showInList": true
}

Created, at the end of the order

Media type application/json
object
id
required
string format: uuid
projectId
required
string
entity
required

What the field is on: a case, a suite, a run, a plan or a step. Its values ride on that entity as custom (for a step, on the Steps Database row, not on the case that carries it). A step field cannot be required.

string
Allowed values: case suite run plan step
name
required

Unique among the fields of the entity in the project, case-insensitively, archived fields included.

string
>= 1 characters <= 100 characters
type
required

Fixed after creation.

string
Allowed values: string number list
multiple
required

A list field that takes several options. Fixed after creation; only a list can be.

boolean
options
required

In the order a person set; null for a string or number field. An option taken off the list stays on the cases that hold it, shown as stale, and is refused only as a NEW value.

Array<object>
nullable <= 100 items
object
id
required

Never changes and is never reused: this is what a case stores. A rename changes label only.

string
label
required
string
>= 1 characters <= 200 characters
archivedAt

Present while the option is archived (#1251), set by the server on archived: true: it is not taken as a new value, and an entity that holds it keeps it.

string format: date-time
required
required

Checked when a case is created. On an existing case the server only refuses to clear the value; it does not demand one.

boolean
defaultValue
required
One of:
string
showInList
required
boolean
showInFilter
required

Independent of showInList (#1250 AC-07). A toggle for the screen, not for access.

boolean
position
required

Dense 0..n-1 among the live fields of the entity with the same role: the configuration groups have an order of their own (#1251).

integer
source
required

Where the field came from (#1930): testrail - the TestRail import made it or carries values into it (the settings page shows “From TestRail”); null - a person made it. Set by the server only: a create or a patch that sends it does not set it, and it never changes once set.

string
nullable
Allowed values: testrail
role
required

configuration — a configuration group (#1251, ADR 0070): a run list of several values, which a launch multiplies into the run’s combinations. Fixed at creation. Its values are archived, never removed; it is neither required nor has a default; position, the limit of 50 and the name are counted among the groups apart from the fields. null — a field.

string
nullable
Allowed values: configuration
archivedAt
required

Set while the field is archived: it takes no new value and its old values stay.

string format: date-time
nullable
createdAt
required
string format: date-time
updatedAt
required
string format: date-time
Example
{
"id": "6f1c2b3a-0000-4000-8000-0000000000f4",
"projectId": "u-shop::default",
"entity": "case",
"name": "Browser",
"type": "list",
"multiple": true,
"options": [
{
"id": "opt_chrome0001",
"label": "Chrome"
},
{
"id": "opt_firefox001",
"label": "Firefox"
}
],
"required": false,
"defaultValue": null,
"showInList": true,
"showInFilter": false,
"position": 0,
"role": null,
"archivedAt": null,
"createdAt": "2026-09-16T09:12:04.151Z",
"updatedAt": "2026-09-16T09:12:04.151Z"
}

Invalid body, a value the field does not take, or a rule broken

Media type application/json
object
error
required
string
Example
{
"error": "validation failed — name: a field needs a name"
}

No / invalid token or session

You are in the project, but not as its owner

Media type application/json
object
error
required
string
Example
{
"error": "project owner only"
}

A field of that name exists for the entity (an archived one counts)

Media type application/json
object
error
required
string
Example
{
"error": "a field named 'Browser' already exists for this entity (an archived one counts)"
}

Two refusals share this status, and what to do about them is opposite. Rate-limited — the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442, #2092): Retry-After carries the seconds until the window rolls over, and waiting that long is the way out. Quota reached — a ceiling on what the project holds (runs in the last 24 hours, test cases, items waiting in the review queue): { error, quota }. A retry straight away frees nothing, so do not loop on it; the message says what makes room — deleting cases, working the queue down, an operator raising the limit — and the run quota, a rolling 24 hours, frees its oldest runs as they age out. Tell the two apart by the body: a 429 whose body has a quota object is a quota, one without it is a throttle. quota carries kind, limit, used, remaining and resetsAt: the figures behind the sentence, and for the run quota the instant its oldest run ages out (#1258). Retry-After is sent on a quota only where there is such an instant (the run quota: the whole seconds, at least 1, until resetsAt); the case, review-queue and project quotas free only by an action, have resetsAt: null, and send no Retry-After. GET /v1/quotas reads the same figures without a refusal.

Media type application/json
One of:
object
error
required
string
Example
{
"quota": {
"kind": "runsPerDay"
}
}
Retry-After
integer

Seconds until the window rolls over on a throttle; on a quota, until quota.resetsAt — sent only for the run quota, which has one