Settings.
The app's settings on the key's project: what each one is, who may write it, the project's value and who set it last. PUT sets the project's value, when the app's manifest lets the API write it.
About settings
- Defined by the app's
manifest.json, created and changed when a commit is packed. The app reads them asArda.config.<key>. This API never creates, renames or deletes one: the areasettingshasrandw, and no other verb. - The project value is what every device of the project is handed, unless it holds a value of its own. Setting it here reaches every live device within seconds; a device that missed the nudge catches up on its next check-in.
revis the project's settings revision: it moves by one with each change, wherever it was made.writtensays who set the project value last:{"via": "portal" | "api", "by": <the person's or the key's name>, "at": …}, ornullfor a value set before 2026-10-19 or seeded from the manifest'sdefault.byisnullwhen that person or key has since been deleted.- A secret is never returned. A field of a
rowssetting markedsecret— a camera's password — is dropped from every row of every answer; the definition still sayssecret: true.
Who may write
Each setting in the manifest may carry "writable": {"portal": true, "api": true, "device": false} — that object is the default.
"api": false | This API reads the value; PUT is 403 NOT_WRITABLE. |
"portal": false | The portal shows the value, locked, and cannot change it — this API still can, if api allows. |
"device": true | The app on a device may write that device's own value with Arda.config.set() (from ArdaPlayer v1.1.17) — never the project value. See the developer manual. |
Device values
A setting with perDevice: true may have a value of its own on a device — set on the device's page in the portal, or by the device's app. This API cannot read or write a device's own value: it has no way to address a device. What it reads and writes is the project value — what every device without a value of its own is handed.
GET/v1/settingsEvery setting of the app on the key's project
Every setting, in manifest order, with its definition, its project value typed as a device receives it, and who set it. The settings here were made for the examples; in this first answer none had been written through the API yet, so written is null.
Key scope settings:r — read settings
Answer 200 The settings
| Field | Type | |
|---|---|---|
rev | integer | The project's settings revision. |
settings | array of Setting |
Errors
| Status | Word | When |
|---|---|---|
| 403 | OUT_OF_SCOPE | The key's scopes do not allow this area and verb (or this dataset, for records). A key edited before an area existed has no entry for it and is refused there until it is edited again in the portal. |
| 404 | NO_SPACE | No space with that id or slug in the key's project — in /v1/spaces/{space}, a space field or parameter, or the Arda-Space header. unassigned is no space (the devices in none). Another project's space answers the same, so nothing can be probed for. |
And, like every keyed call: NO_KEY BAD_KEY KEY_REVOKED KEY_EXPIRED ACCOUNT_INACTIVE NO_API_ACCESS RATE_LIMITED INTERNAL.
curl "https://api.ardaform.net/v1/settings" \
-H "Authorization: Bearer $ARDA_KEY"const res = await fetch('https://api.ardaform.net/v1/settings', {
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`
}
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.get(
"https://api.ardaform.net/v1/settings",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 102,
"settings": [
{
"key": "zz_docs_shop_name",
"caption": "Shop name",
"type": "text",
"help": "Printed at the top of every receipt",
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": null,
"value": "Bristol",
"written": null
},
{
"key": "zz_docs_dark_mode",
"caption": "Dark mode",
"type": "bool",
"help": null,
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": true
},
"options": null,
"value": false,
"written": null
},
{
"key": "zz_docs_currency",
"caption": "Currency",
"type": "select",
"help": null,
"perDevice": false,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": [
{
"value": "gbp",
"caption": "Pounds"
},
{
"value": "eur",
"caption": "Euros"
}
],
"value": "gbp",
"written": null
},
{
"key": "zz_docs_idle_after",
"caption": "Back to start after",
"type": "number",
"help": null,
"perDevice": false,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": {
"unit": "s"
},
"value": 60,
"written": null
},
{
"key": "zz_docs_tags",
"caption": "Tags",
"type": "list",
"help": null,
"perDevice": false,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": null,
"value": [
"front",
"bakery"
],
"written": null
},
{
"key": "zz_docs_cameras",
"caption": "Cameras",
"type": "rows",
"help": null,
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": {
"min": 0,
"max": 6,
"item": "Camera",
"fields": [
{
"key": "name",
"caption": "Name",
"type": "text",
"required": true
},
{
"key": "url",
"caption": "Stream address",
"type": "text",
"required": true,
"secret": true
}
]
},
"value": [
{
"name": "Door"
}
],
"written": null
},
{
"key": "zz_docs_licence",
"caption": "Licence number",
"type": "text",
"help": null,
"perDevice": false,
"writable": {
"portal": false,
"api": false,
"device": false
},
"options": null,
"value": "LIC-0042",
"written": null
}
]
}
GET/v1/settings/{key}One setting
One setting by its key. A key that is not lower-case letters, digits and _ starting with a letter cannot be a setting: 404 NO_SETTING.
Key scope settings:r — read settings
Path parameters
| Name | Type | |
|---|---|---|
key | string pattern ^[a-z][a-z0-9_]{0,63}$ | The setting's key — what the app reads as Arda.config.<key>. Anything not of this form is 404 NO_SETTING. |
Answer 200 The setting
| Field | Type | |
|---|---|---|
rev | integer | The project's settings revision. |
setting | Setting | One of the app's settings on the key's project — defined by the app's manifest.json, read by the app as Arda.config.<key> — with the PROJECT value and who wrote it last. A device's own value is not here. |
Errors
| Status | Word | When |
|---|---|---|
| 403 | OUT_OF_SCOPE | The key's scopes do not allow this area and verb (or this dataset, for records). A key edited before an area existed has no entry for it and is refused there until it is edited again in the portal. |
| 404 | NO_SETTING | No setting with that key on the key's project (keys are a-z, digits and _, starting with a letter). |
| 404 | NO_SPACE | No space with that id or slug in the key's project — in /v1/spaces/{space}, a space field or parameter, or the Arda-Space header. unassigned is no space (the devices in none). Another project's space answers the same, so nothing can be probed for. |
And, like every keyed call: NO_KEY BAD_KEY KEY_REVOKED KEY_EXPIRED ACCOUNT_INACTIVE NO_API_ACCESS RATE_LIMITED INTERNAL.
curl "https://api.ardaform.net/v1/settings/zz_docs_dark_mode" \
-H "Authorization: Bearer $ARDA_KEY"const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_dark_mode', {
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`
}
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.get(
"https://api.ardaform.net/v1/settings/zz_docs_dark_mode",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 102,
"setting": {
"key": "zz_docs_dark_mode",
"caption": "Dark mode",
"type": "bool",
"help": null,
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": true
},
"options": null,
"value": false,
"written": null
}
}
A key the app does not define
curl "https://api.ardaform.net/v1/settings/zz_docs_nothing" \
-H "Authorization: Bearer $ARDA_KEY"const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_nothing', {
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`
}
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.get(
"https://api.ardaform.net/v1/settings/zz_docs_nothing",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"error": {
"code": "NO_SETTING",
"message": "No setting with the key 'zz_docs_nothing' in this project."
}
}
PUT/v1/settings/{key}Set the project's value of one setting
Sets the project value: the body is {"value": …}, with the value in the setting's own JSON type, or null to clear it (a device's app then falls back to its own default).
- Checked in this order: the key's
settings:w, the setting exists, the manifest lets the API write it, the body hasvalue, the value fits. - A value equal to the stored one writes nothing:
changed: false,revunmoved,writtenas it was. - ⚠️ A
rowssetting with asecretfield is replaced whole, and the answer never shows the secret: send every row with its secret each time. A list read back and sent unchanged loses the secret — or is refused, when the field isrequired.
Key scope settings:w — write settings
Path parameters
| Name | Type | |
|---|---|---|
key | string pattern ^[a-z][a-z0-9_]{0,63}$ | The setting's key — what the app reads as Arda.config.<key>. Anything not of this form is 404 NO_SETTING. |
Body
| Field | Type | |
|---|---|---|
value required | any | The value in the setting's own type, or null to clear the project value. |
Answer 200 Saved, or already so
| Field | Type | |
|---|---|---|
rev | integer | The project's settings revision after the call. |
changed | boolean | False when the value was already that — nothing was written. |
setting | Setting | One of the app's settings on the key's project — defined by the app's manifest.json, read by the app as Arda.config.<key> — with the PROJECT value and who wrote it last. A device's own value is not here. |
Errors
| Status | Word | When |
|---|---|---|
| 400 | BAD_REQUEST | The body is missing a required field, has one of the wrong type or length, or (on PUT /v1/content/…) arrived shorter or longer than its Content-Length. The message says which. |
| 400 | INVALID_VALUE | The value does not fit the setting's type. The message gives the reason. |
| 403 | NOT_WRITABLE | The app's manifest says "writable": {"api": false} for this setting — or the setting is a select whose choices are each device's own ("options": {"device": …}), chosen on each device — or, on PATCH /v1/devices/{uid}/policy, the manifest's screen.writable.api is false and orientation was sent. Read it; change it in the portal. |
| 403 | OUT_OF_SCOPE | The key's scopes do not allow this area and verb (or this dataset, for records). A key edited before an area existed has no entry for it and is refused there until it is edited again in the portal. |
| 404 | NO_SETTING | No setting with that key on the key's project (keys are a-z, digits and _, starting with a letter). |
| 404 | NO_SPACE | No space with that id or slug in the key's project — in /v1/spaces/{space}, a space field or parameter, or the Arda-Space header. unassigned is no space (the devices in none). Another project's space answers the same, so nothing can be probed for. |
And, like every keyed call: NO_KEY BAD_KEY KEY_REVOKED KEY_EXPIRED ACCOUNT_INACTIVE NO_API_ACCESS RATE_LIMITED INTERNAL BAD_JSON TOO_LARGE.
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_shop_name" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":"Bristol Harbourside"}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_shop_name', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: 'Bristol Harbourside' })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_shop_name",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": "Bristol Harbourside"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 103,
"changed": true,
"setting": {
"key": "zz_docs_shop_name",
"caption": "Shop name",
"type": "text",
"help": "Printed at the top of every receipt",
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": null,
"value": "Bristol Harbourside",
"written": {
"via": "api",
"by": "api npm test",
"at": "2026-10-27T20:10:17.357Z"
}
}
}
The same value again
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_shop_name" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":"Bristol Harbourside"}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_shop_name', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: 'Bristol Harbourside' })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_shop_name",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": "Bristol Harbourside"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 103,
"changed": false,
"setting": {
"key": "zz_docs_shop_name",
"caption": "Shop name",
"type": "text",
"help": "Printed at the top of every receipt",
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": null,
"value": "Bristol Harbourside",
"written": {
"via": "api",
"by": "api npm test",
"at": "2026-10-27T20:10:17.357Z"
}
}
}
A rows value, with a secret field
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_cameras" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":[{"name":"Door","url":"rtsp://viewer:[email protected]/stream1"},{"name":"Till","url":"rtsp://viewer:[email protected]/stream1"}]}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_cameras', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
value: [
{ name: 'Door', url: 'rtsp://viewer:[email protected]/stream1' },
{ name: 'Till', url: 'rtsp://viewer:[email protected]/stream1' }
]
})
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_cameras",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={
"value": [
{
"name": "Door",
"url": "rtsp://viewer:[email protected]/stream1",
},
{
"name": "Till",
"url": "rtsp://viewer:[email protected]/stream1",
},
],
},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 104,
"changed": true,
"setting": {
"key": "zz_docs_cameras",
"caption": "Cameras",
"type": "rows",
"help": null,
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": {
"min": 0,
"max": 6,
"item": "Camera",
"fields": [
{
"key": "name",
"caption": "Name",
"type": "text",
"required": true
},
{
"key": "url",
"caption": "Stream address",
"type": "text",
"required": true,
"secret": true
}
]
},
"value": [
{
"name": "Door"
},
{
"name": "Till"
}
],
"written": {
"via": "api",
"by": "api npm test",
"at": "2026-10-27T20:10:17.542Z"
}
}
}
A list
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_tags" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":["front","bakery","late"]}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_tags', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: ['front', 'bakery', 'late'] })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_tags",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": ["front", "bakery", "late"]},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 105,
"changed": true,
"setting": {
"key": "zz_docs_tags",
"caption": "Tags",
"type": "list",
"help": null,
"perDevice": false,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": null,
"value": [
"front",
"bakery",
"late"
],
"written": {
"via": "api",
"by": "api npm test",
"at": "2026-10-27T20:10:17.619Z"
}
}
}
A setting the manifest keeps from the API
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_licence" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":"LIC-0043"}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_licence', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: 'LIC-0043' })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_licence",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": "LIC-0043"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"error": {
"code": "NOT_WRITABLE",
"message": "'zz_docs_licence' is read-only through the API: the app's manifest.json says \"writable\": {\"api\": false}.",
"key": "zz_docs_licence"
}
}
A number sent as text
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_idle_after" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":"90"}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_idle_after', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: '90' })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_idle_after",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": "90"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"error": {
"code": "INVALID_VALUE",
"message": "'zz_docs_idle_after' must be a number.",
"key": "zz_docs_idle_after"
}
}
Not one of a select's choices
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_currency" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":"usd"}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_currency', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: 'usd' })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_currency",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": "usd"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"error": {
"code": "INVALID_VALUE",
"message": "'zz_docs_currency' must be one of: gbp, eur.",
"key": "zz_docs_currency"
}
}
Not {"value": …}
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_shop_name" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"val":"Bristol"}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_shop_name', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ val: 'Bristol' })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_shop_name",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"val": "Bristol"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"error": {
"code": "BAD_REQUEST",
"message": "Send {\"value\": …} as JSON: the value in its own type, or null to clear the project's value."
}
}
null clears the project value
curl -X PUT "https://api.ardaform.net/v1/settings/zz_docs_shop_name" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"value":null}'const res = await fetch('https://api.ardaform.net/v1/settings/zz_docs_shop_name', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ value: null })
})
const answer = await res.json()
if (!res.ok) throw new Error(`${res.status} ${answer.error.code}: ${answer.error.message}`)
console.log(answer)import os
import requests
r = requests.put(
"https://api.ardaform.net/v1/settings/zz_docs_shop_name",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"value": None},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"rev": 106,
"changed": true,
"setting": {
"key": "zz_docs_shop_name",
"caption": "Shop name",
"type": "text",
"help": "Printed at the top of every receipt",
"perDevice": true,
"writable": {
"portal": true,
"api": true,
"device": false
},
"options": null,
"value": null,
"written": {
"via": "api",
"by": "api npm test",
"at": "2026-10-27T20:10:18.156Z"
}
}
}
Values by type
A program is writing, not a form: nothing is converted. The same rules as a device's own write.
type | A value is | Refused, for example |
|---|---|---|
text memo image | a string, at most 64 KB | 5 |
number | a JSON number | "90" |
bool | true or false | "true", 1 |
select | one of its choices' value, a string | a caption, a value not listed |
color | "#rrggbb" | "red", "#fff" |
list | an array of strings | "a,b", [1, 2] |
rows | an array of objects with only the declared fields, each of its type, every required one present, between the setting's min and max rows (default 0–100) | an unknown field, a missing required one, too many rows |
A rows value is stored cleaned: fields in their declared order, an empty optional field left out. Answers type the value as a device receives it — a list is an array, a number a number, a bool a boolean.
The Setting shape
Setting
One of the app's settings on the key's project — defined by the app's manifest.json, read by the app as Arda.config.<key> — with the PROJECT value and who wrote it last. A device's own value is not here.
| Field | Type | |
|---|---|---|
key | string | What the app reads, Arda.config.<key>, and how you address it. Defined by the manifest and frozen there. |
caption | string | For people. |
type | string — text or memo or number or bool or select or color or image or list or rows | What the value is. The manifest's word: select, never the stored enum. |
help | string, nullable | The manifest's sentence for whoever sets it, or null. |
perDevice | boolean | Whether a device may hold a value of its own, set in the portal or written by its app. A device's own value is not addressable through this API. |
writable | object | Who may write the value — the manifest's writable, default {"portal": true, "api": true, "device": false}. |
writable.portal | boolean | False: the portal shows the value and cannot change it. |
writable.api | boolean | False: this API reads it, and PUT is 403 NOT_WRITABLE. |
writable.device | boolean | True: the app on a device may write THAT DEVICE'S own value (Arda.config.set), never this project value. |
options | object, nullable | The definition: a select's choices [{value, caption}]; a number's {unit}; a rows setting's {min, max, item, fields}, each field flagged required / secret as the manifest says. Null for the rest. |
device | string — printers or screens | Only on a select whose choices are EACH DEVICE'S OWN — the manifest's "options": {"device": "printers"}: which list of what a device reports they come from. options is then [], the setting is per device, and its value is chosen on each device in the portal (or by the device's app); PUT is 403 NOT_WRITABLE. Absent on every other setting. |
value | any | The PROJECT value as a device is handed it: bool true/false, number a number, list an array of words, rows an array of objects, the rest a string. Null when it is not set — then a device's app falls back to its own default. ⚠️ A rows field marked secret is NEVER returned: it is dropped from every row, so what this answers is not a value to send back whole. |
written | object, nullable | Who wrote the project value last, or null — written before 2026-10-19, or seeded from the manifest's default. |
written.via | string — portal or api or device | Where it was written: in the portal, through this API, or (for a device's own value, never this one) by a device. |
written.by | string, nullable | The person's name in the portal, the API key's name, or the device's name. Null when that writer has since been deleted. |
written.at | string (date-time) | When. UTC. |