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.

Working — v1 at api.ardaform.net

About settings

  • Defined by the app's manifest.json, created and changed when a commit is packed. The app reads them as Arda.config.<key>. This API never creates, renames or deletes one: the area settings has r and w, 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.
  • rev is the project's settings revision: it moves by one with each change, wherever it was made.
  • written says who set the project value last: {"via": "portal" | "api", "by": <the person's or the key's name>, "at": …}, or null for a value set before 2026-10-19 or seeded from the manifest's default. by is null when that person or key has since been deleted.
  • A secret is never returned. A field of a rows setting marked secret — a camera's password — is dropped from every row of every answer; the definition still says secret: true.

Who may write

Each setting in the manifest may carry "writable": {"portal": true, "api": true, "device": false} — that object is the default.

"api": falseThis API reads the value; PUT is 403 NOT_WRITABLE.
"portal": falseThe portal shows the value, locked, and cannot change it — this API still can, if api allows.
"device": trueThe 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

FieldType
revintegerThe project's settings revision.
settingsarray of Setting

Errors

StatusWordWhen
403OUT_OF_SCOPEThe 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.
404NO_SPACENo 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"
Answer · 200 OKran live 2026-10-27 · test project
{
  "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

NameType
keystring
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

FieldType
revintegerThe project's settings revision.
settingSettingOne 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

StatusWordWhen
403OUT_OF_SCOPEThe 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.
404NO_SETTINGNo setting with that key on the key's project (keys are a-z, digits and _, starting with a letter).
404NO_SPACENo 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"
Answer · 200 OKran live 2026-10-27 · test project
{
  "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"
Answer · 404 Not Foundran live 2026-10-27 · test project
{
  "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 has value, the value fits.
  • A value equal to the stored one writes nothing: changed: false, rev unmoved, written as it was.
  • ⚠️ A rows setting with a secret field 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 is required.

Key scope settings:w — write settings

Path parameters

NameType
keystring
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

FieldType
value requiredanyThe value in the setting's own type, or null to clear the project value.

Answer 200 Saved, or already so

FieldType
revintegerThe project's settings revision after the call.
changedbooleanFalse when the value was already that — nothing was written.
settingSettingOne 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

StatusWordWhen
400BAD_REQUESTThe 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.
400INVALID_VALUEThe value does not fit the setting's type. The message gives the reason.
403NOT_WRITABLEThe 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.
403OUT_OF_SCOPEThe 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.
404NO_SETTINGNo setting with that key on the key's project (keys are a-z, digits and _, starting with a letter).
404NO_SPACENo 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"}'
Answer · 200 OKran live 2026-10-27 · test project
{
  "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"}'
Answer · 200 OKran live 2026-10-27 · test project
{
  "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"}]}'
Answer · 200 OKran live 2026-10-27 · test project
{
  "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"]}'
Answer · 200 OKran live 2026-10-27 · test project
{
  "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"}'
Answer · 403 Forbiddenran live 2026-10-27 · test project
{
  "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"}'
Answer · 400 Bad Requestran live 2026-10-27 · test project
{
  "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"}'
Answer · 400 Bad Requestran live 2026-10-27 · test project
{
  "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"}'
Answer · 400 Bad Requestran live 2026-10-27 · test project
{
  "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}'
Answer · 200 OKran live 2026-10-27 · test project
{
  "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.

typeA value isRefused, for example
text memo imagea string, at most 64 KB5
numbera JSON number"90"
booltrue or false"true", 1
selectone of its choices' value, a stringa caption, a value not listed
color"#rrggbb""red", "#fff"
listan array of strings"a,b", [1, 2]
rowsan 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.

FieldType
keystringWhat the app reads, Arda.config.<key>, and how you address it. Defined by the manifest and frozen there.
captionstringFor people.
typestring — text or memo or number or bool or select or color or image or list or rowsWhat the value is. The manifest's word: select, never the stored enum.
helpstring, nullableThe manifest's sentence for whoever sets it, or null.
perDevicebooleanWhether 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.
writableobjectWho may write the value — the manifest's writable, default {"portal": true, "api": true, "device": false}.
writable.portalbooleanFalse: the portal shows the value and cannot change it.
writable.apibooleanFalse: this API reads it, and PUT is 403 NOT_WRITABLE.
writable.devicebooleanTrue: the app on a device may write THAT DEVICE'S own value (Arda.config.set), never this project value.
optionsobject, nullableThe 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.
devicestring — printers or screensOnly 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.
valueanyThe 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.
writtenobject, nullableWho wrote the project value last, or null — written before 2026-10-19, or seeded from the manifest's default.
written.viastring — portal or api or deviceWhere it was written: in the portal, through this API, or (for a device's own value, never this one) by a device.
written.bystring, nullableThe 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.atstring (date-time)When. UTC.