Devices.
The devices of the key's project: who each one is, whether it is connected, and the policy ArdaForm holds for it — two switches and the screen's orientation. PATCH sets them; they reach the device at once when it is online, else when it next connects.
About devices
- A device is added by the code it shows — typed into the portal, or sent to
POST /v1/devices/activate— and removed in the portal. Besides activating one, this API lists the project's devices, reads one, moves one into a space, and sets itspolicy. The areadeviceshasrandw, and no other verb. A key made before 2026-10-24 has nodevicesentry in its scopes until it is edited in the portal. - Every device is in a space, or in none. Its
spacedecides the branch it runs and — a private space — whose data it reads;nullis Unassigned: it runs no app, and its screen says it is waiting for a space.?space=lists one space's devices: anidorslug,default, orunassigned. - The key names the project. A uid on another project answers
404 NO_DEVICE, exactly like a uid that does not exist. presenceis the word the portal's screens use:online,offline, ornever(never connected).onlineis the same answer as a boolean.- A removed device stays in the list with
enrolled: false, so a script that knew it can see what became of it.
The policy
A small JSON object of optional keys — two switches and the screen's orientation — kept per device and re-stated to the device on every check-in. Today an Android device acts on it; a Linux device accepts it and ignores it.
exitAllowed | Whether Shut down in the portal — and the app's Arda.system.shutdown() — may close the player. The device's own default is true. false: both are refused on the device, and the portal's Shut down dims. |
kiosk | Lock the screen to the player: no status bar, Home or Recents. Needs the player set up as device owner on the box; a box that is not keeps running as an ordinary app and says so in android.deviceOwner. The device's own default is false. |
orientation | Which way the screen is drawn: portrait, landscape, or auto — follow the device, its own default. For tablets and anything else that turns. |
- An absent key means no opinion: the device keeps what it holds.
PATCHchanges only the keys it is sent, and a key cannot be cleared.{}is a policy that says nothing. - Two words, shown side by side.
policyis what ArdaForm holds;android(on one device) is what the device last reported —kioskthe screen's live lock state,exitAllowedwhat it applies now. They differ until the device has checked in. - Takes effect at once when the device is online, else at its next connection. The answer's
appliessays which. - The app's manifest has a say on the orientation. A top-level
"screen": {"orientation": "landscape", "writable": {"portal": true, "api": true}}presets it — what a device follows while nothing is saved for it — and may lock it:"api": falsemakes aPATCHwithorientationa403 NOT_WRITABLE,"portal": falselocks the row in the portal. A locked orientation is the manifest's on every device whatever is saved;policyhere is always what is saved. An Android device turns its screen to it from ArdaPlayer v1.3.0; a Linux device keeps the fact.
GET/v1/devicesEvery device of the key's project, or of one space
Every device of the key's project, by name, with the space each is in — or one space's, with ?space=. The device here was made for the examples and has never connected, so presence is never and nobody has set a policy on it yet: {}.
Key scope devices:r — read devices
Query parameters
| Name | Type | |
|---|---|---|
space | string at most 100 characters | Only the devices of one space: its id or slug, default for the Default, or unassigned for the devices in none. Absent: every device of the project (or of the space Arda-Space names). |
Answer 200 The devices
| Field | Type | |
|---|---|---|
devices | array of object |
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/devices" \
-H "Authorization: Bearer $ARDA_KEY"const res = await fetch('https://api.ardaform.net/v1/devices', {
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/devices",
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){
"devices": [
{
"uid": "aa11aa11aa11aa11aa11a9e5",
"name": "zz-docs-tablet",
"os": "android",
"arch": "arm64",
"model": "Samsung SM-X200",
"player": "v1.2.4",
"branch": "main",
"partition": null,
"location": "Counter 1",
"enrolled": false,
"space": {
"id": 1,
"slug": "default",
"name": "Default"
},
"presence": "never",
"online": false,
"last_seen_at": null,
"policy": {}
}
]
}
GET/v1/devices/{uid}One device, with what it last reported
One device, with android beside it on an Android device: what the device last reported about itself. Null on a Linux device. A malformed uid, or one that is not on this project, is 404 NO_DEVICE.
Key scope devices:r — read devices
Path parameters
| Name | Type | |
|---|---|---|
uid | string pattern ^[0-9a-f]{24}$ | The device's id — 24 hex characters, shown on its page in the portal. Anything else is 404 NO_DEVICE. |
Answer 200 The device
| Field | Type | |
|---|---|---|
device | object | One device of the key's project, as ArdaForm knows it — its identity, whether it is connected, and the policy ArdaForm holds for it. On one device, android is what the device last reported about itself. |
device.uid | string | The device's id — 24 hex characters, the one on its page in the portal. How you address it. |
device.name | string | What it was named in the portal. |
device.os | string, nullable | The player's operating system as it reports it: linux, android, … |
device.arch | string, nullable | The processor family: arm64, amd64, … |
device.model | string, nullable | The hardware, as the device reports it. |
device.player | string, nullable | The ArdaPlayer version it last reported. |
device.branch | string, nullable | The code branch it runs — its space's. Null when it is Unassigned: it runs no app. |
device.partition | string, nullable | Its data partition, or null when it has none. |
device.location | string, nullable | Where it is, as typed in the portal. Null when nothing was typed. |
device.enrolled | boolean | False once it was removed (wiped), or never activated — its record stays. |
device.space | object, nullable | The space it is in: id, slug, name. Null when it is in none — Unassigned. |
device.presence | string — online or offline or never, nullable | The one word every portal screen uses: online, offline, or never (never connected). |
device.online | boolean | Connected to ArdaForm right now. |
device.last_seen_at | string (date-time), nullable | When it was last heard from. Null when never. UTC. |
device.policy | object | The switches — and the screen's orientation — ArdaForm holds for this device and re-states to it on every check-in — only the keys that were ever set, {} when none. A key that is absent means the device keeps what it holds. |
device.android | object, nullable | Only on GET /v1/devices/{uid}, and only on an Android device: what the device last REPORTED about itself (its sysinfo.android) — kiosk is the screen's live lock state, exitAllowed what it applies now, deviceOwner whether kiosk can work at all, with release, model, appVersion and battery. It differs from policy until the device has checked in. Null on a Linux device; absent in the list. |
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_DEVICE | No device with that uid on the key's project. A uid is 24 hex characters; a device on another project answers the same, so nothing can be probed for. |
| 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/devices/aa11aa11aa11aa11aa11a9e5" \
-H "Authorization: Bearer $ARDA_KEY"const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5', {
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/devices/aa11aa11aa11aa11aa11a9e5",
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){
"device": {
"uid": "aa11aa11aa11aa11aa11a9e5",
"name": "zz-docs-tablet",
"os": "android",
"arch": "arm64",
"model": "Samsung SM-X200",
"player": "v1.2.4",
"branch": "main",
"partition": null,
"location": "Counter 1",
"enrolled": false,
"space": {
"id": 1,
"slug": "default",
"name": "Default"
},
"presence": "never",
"online": false,
"last_seen_at": null,
"policy": {},
"android": {
"sdk": 34,
"release": "14",
"model": "Samsung SM-X200",
"abi": "arm64-v8a",
"appVersion": "1.2.4",
"appCode": 1020400,
"deviceOwner": true,
"kiosk": false,
"kioskWanted": false,
"home": true,
"exitAllowed": true,
"screen": {
"w": 1280,
"h": 800,
"dpi": 240
},
"battery": {
"pct": 87,
"charging": true
}
}
}
}
A uid on another project, or none
curl "https://api.ardaform.net/v1/devices/4acf91e8d59988744ea33baa" \
-H "Authorization: Bearer $ARDA_KEY"const res = await fetch('https://api.ardaform.net/v1/devices/4acf91e8d59988744ea33baa', {
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/devices/4acf91e8d59988744ea33baa",
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_DEVICE",
"message": "No device with the uid '4acf91e8d59988744ea33baa' on this project."
}
}
PATCH/v1/devices/{uid}Move a device into a space
Moves the device into a space: {"space": "<id or slug>"}, "default", or "unassigned" for none. It takes the space's branch at once. Into unassigned it stops running its app and its screen says it is waiting for a space; moved into one again, the app comes back. Into or out of a private space it drops its data, its data partition is cleared, and it walks the new space's data again; between spaces that share the project's data nothing is walked again. A device that is away does it when it next connects — applies says which. A space holding its max_devices is 409 SPACE_FULL; moved: false when it was already there.
Key scope devices:w — write devices
Path parameters
| Name | Type | |
|---|---|---|
uid | string pattern ^[0-9a-f]{24}$ | The device's id — 24 hex characters, shown on its page in the portal. Anything else is 404 NO_DEVICE. |
Body
| Field | Type | |
|---|---|---|
space required | string, nullable | The space: id, slug, default, or unassigned / null for none. |
Answer 200 Moved, or already there
| Field | Type | |
|---|---|---|
device | object | One device of the key's project, as ArdaForm knows it — its identity, whether it is connected, and the policy ArdaForm holds for it. On one device, android is what the device last reported about itself. |
device.uid | string | The device's id — 24 hex characters, the one on its page in the portal. How you address it. |
device.name | string | What it was named in the portal. |
device.os | string, nullable | The player's operating system as it reports it: linux, android, … |
device.arch | string, nullable | The processor family: arm64, amd64, … |
device.model | string, nullable | The hardware, as the device reports it. |
device.player | string, nullable | The ArdaPlayer version it last reported. |
device.branch | string, nullable | The code branch it runs — its space's. Null when it is Unassigned: it runs no app. |
device.partition | string, nullable | Its data partition, or null when it has none. |
device.location | string, nullable | Where it is, as typed in the portal. Null when nothing was typed. |
device.enrolled | boolean | False once it was removed (wiped), or never activated — its record stays. |
device.space | object, nullable | The space it is in: id, slug, name. Null when it is in none — Unassigned. |
device.presence | string — online or offline or never, nullable | The one word every portal screen uses: online, offline, or never (never connected). |
device.online | boolean | Connected to ArdaForm right now. |
device.last_seen_at | string (date-time), nullable | When it was last heard from. Null when never. UTC. |
device.policy | object | The switches — and the screen's orientation — ArdaForm holds for this device and re-states to it on every check-in — only the keys that were ever set, {} when none. A key that is absent means the device keeps what it holds. |
device.android | object, nullable | Only on GET /v1/devices/{uid}, and only on an Android device: what the device last REPORTED about itself (its sysinfo.android) — kiosk is the screen's live lock state, exitAllowed what it applies now, deviceOwner whether kiosk can work at all, with release, model, appVersion and battery. It differs from policy until the device has checked in. Null on a Linux device; absent in the list. |
moved | boolean | False when it was already in that space — nothing changed. |
applies | string — now or when the device next connects | Whether the device was connected at the time. |
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 | NOTHING_TO_DO | A PATCH sent no field it can change (a dataset with none of its fields; a device policy with no key). |
| 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_DEVICE | No device with that uid on the key's project. A uid is 24 hex characters; a device on another project answers the same, so nothing can be probed for. |
| 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. |
| 409 | SPACE_FULL | The space already holds its max_devices. |
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 PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"space":"zz-docs-cake-co"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ space: 'zz-docs-cake-co' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"space": "zz-docs-cake-co"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"device": {
"uid": "aa11aa11aa11aa11aa11a9e5",
"name": "zz-docs-tablet",
"os": "android",
"arch": "arm64",
"model": "Samsung SM-X200",
"player": "v1.2.4",
"branch": "main",
"partition": null,
"location": "Counter 1",
"enrolled": false,
"space": {
"id": 850,
"slug": "zz-docs-cake-co",
"name": "Cake & Co, Soho Road"
},
"presence": "never",
"online": false,
"last_seen_at": null,
"policy": {
"exitAllowed": false,
"kiosk": true,
"orientation": "landscape"
}
},
"moved": true,
"applies": "when the device next connects"
}
Back to the Default
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"space":"default"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ space: 'default' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"space": "default"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"device": {
"uid": "aa11aa11aa11aa11aa11a9e5",
"name": "zz-docs-tablet",
"os": "android",
"arch": "arm64",
"model": "Samsung SM-X200",
"player": "v1.2.4",
"branch": "main",
"partition": null,
"location": "Counter 1",
"enrolled": false,
"space": {
"id": 1,
"slug": "default",
"name": "Default"
},
"presence": "never",
"online": false,
"last_seen_at": null,
"policy": {
"exitAllowed": false,
"kiosk": true,
"orientation": "landscape"
}
},
"moved": true,
"applies": "when the device next connects"
}
PATCH/v1/devices/{uid}/policySet a device's switches and screen
Sets the policy ArdaForm holds for the device: the body is any of {"exitAllowed": true|false}, {"kiosk": true|false} and {"orientation": "portrait"|"landscape"|"auto"}. The answer is the whole saved policy after the merge.
- Checked in this order: the key's
devices:w, the device exists, the body is an object with only policy keys — each switch a JSON boolean, the orientation one of the three words — and, fororientation, the app's manifest does not say"screen": {"writable": {"api": false}}(403 NOT_WRITABLE; the switches beside it still write). - A policy that already says so writes nothing:
changed: false. appliesisnowwhen the device was connected — it is told straight away — orwhen the device next connects. Either way the next check-in re-states it, so nothing is lost.- Read it back on the device itself (
GET /v1/devices/{uid}); there is noGETon/policy.
Key scope devices:w — write devices
Path parameters
| Name | Type | |
|---|---|---|
uid | string pattern ^[0-9a-f]{24}$ | The device's id — 24 hex characters, shown on its page in the portal. Anything else is 404 NO_DEVICE. |
Body
| Field | Type | |
|---|---|---|
exitAllowed | boolean | Whether Shut down, and the app's Arda.system.shutdown(), may close the player on this device. |
kiosk | boolean | Lock the screen to the player. Needs the player set up as device owner on the box. |
orientation | string — portrait or landscape or auto | Which way the screen is drawn; auto follows the device. Refused while the app's manifest says screen.writable.api: false. |
Answer 200 Saved, or already so
| Field | Type | |
|---|---|---|
uid | string | The device. |
changed | boolean | False when the policy already said so — nothing was written. |
policy | object | The switches — and the screen's orientation — ArdaForm holds for this device and re-states to it on every check-in — only the keys that were ever set, {} when none. A key that is absent means the device keeps what it holds. |
policy.exitAllowed | boolean | Android: whether Shut down — and the app's Arda.system.shutdown() — may close the player. The device's own default is true. False: both are refused on the device, and the portal's Shut down dims. |
policy.kiosk | boolean | Android: lock the screen to the player — no status bar, Home or Recents. Needs the player set up as device owner on the box; a box that is not keeps running as an ordinary app. The device's own default is false. |
policy.orientation | string — portrait or landscape or auto | Which way the screen is drawn: portrait, landscape, or auto — follow the device (its own default). The app's manifest may preset it (screen.orientation) and lock it (screen.writable); a locked orientation is the manifest's whatever is saved here. Android devices act on it today. |
applies | string — now or when the device next connects | Whether the device was connected at the time — told now — or is told when it next connects. |
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 | NOTHING_TO_DO | A PATCH sent no field it can change (a dataset with none of its fields; a device policy with no key). |
| 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_DEVICE | No device with that uid on the key's project. A uid is 24 hex characters; a device on another project answers the same, so nothing can be probed for. |
| 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 PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"exitAllowed":false}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ exitAllowed: false })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"exitAllowed": False},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"uid": "aa11aa11aa11aa11aa11a9e5",
"changed": true,
"policy": {
"exitAllowed": false
},
"applies": "when the device next connects"
}
The other key — the first is kept
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"kiosk":true}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ kiosk: true })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"kiosk": True},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"uid": "aa11aa11aa11aa11aa11a9e5",
"changed": true,
"policy": {
"exitAllowed": false,
"kiosk": true
},
"applies": "when the device next connects"
}
The same again
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"kiosk":true}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ kiosk: true })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"kiosk": True},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"uid": "aa11aa11aa11aa11aa11a9e5",
"changed": false,
"policy": {
"exitAllowed": false,
"kiosk": true
},
"applies": "when the device next connects"
}
Which way the screen is drawn
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"orientation":"landscape"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ orientation: 'landscape' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"orientation": "landscape"},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"uid": "aa11aa11aa11aa11aa11a9e5",
"changed": true,
"policy": {
"exitAllowed": false,
"kiosk": true,
"orientation": "landscape"
},
"applies": "when the device next connects"
}
Not a JSON boolean
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"exitAllowed":"false"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ exitAllowed: 'false' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"exitAllowed": "false"},
)
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": "'exitAllowed' must be true or false — a JSON boolean, not \"false\". A key cannot be cleared."
}
}
Not one of the three words
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"orientation":"sideways"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ orientation: 'sideways' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"orientation": "sideways"},
)
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": "'orientation' is one of \"portrait\", \"landscape\" or \"auto\" — not \"sideways\". A key cannot be cleared."
}
}
Locked by the manifest
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"orientation":"auto"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ orientation: 'auto' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"orientation": "auto"},
)
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": "'orientation' is read-only through the API: the app's manifest.json sets the screen to portrait and says \"writable\": {\"api\": false}.",
"key": "orientation"
}
}
A key the policy does not have
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{"screen":"off"}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ screen: 'off' })
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={"screen": "off"},
)
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": "'screen' is not a policy key. The keys are `exitAllowed`, `kiosk`, `orientation`."
}
}
No key at all
curl -X PATCH "https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy" \
-H "Authorization: Bearer $ARDA_KEY" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.ARDA_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({})
})
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.patch(
"https://api.ardaform.net/v1/devices/aa11aa11aa11aa11aa11a9e5/policy",
headers={"Authorization": f"Bearer {os.environ['ARDA_KEY']}"},
json={},
)
answer = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code} {answer['error']['code']}: {answer['error']['message']}")
print(answer){
"error": {
"code": "NOTHING_TO_DO",
"message": "The body names no policy key. Send {\"exitAllowed\": true|false}, {\"kiosk\": true|false} and/or {\"orientation\": \"portrait\"|\"landscape\"|\"auto\"}."
}
}
The Device shape
Device
One device of the key's project, as ArdaForm knows it — its identity, whether it is connected, and the policy ArdaForm holds for it. On one device, android is what the device last reported about itself.
| Field | Type | |
|---|---|---|
uid | string | The device's id — 24 hex characters, the one on its page in the portal. How you address it. |
name | string | What it was named in the portal. |
os | string, nullable | The player's operating system as it reports it: linux, android, … |
arch | string, nullable | The processor family: arm64, amd64, … |
model | string, nullable | The hardware, as the device reports it. |
player | string, nullable | The ArdaPlayer version it last reported. |
branch | string, nullable | The code branch it runs — its space's. Null when it is Unassigned: it runs no app. |
partition | string, nullable | Its data partition, or null when it has none. |
location | string, nullable | Where it is, as typed in the portal. Null when nothing was typed. |
enrolled | boolean | False once it was removed (wiped), or never activated — its record stays. |
space | object, nullable | The space it is in: id, slug, name. Null when it is in none — Unassigned. |
presence | string — online or offline or never, nullable | The one word every portal screen uses: online, offline, or never (never connected). |
online | boolean | Connected to ArdaForm right now. |
last_seen_at | string (date-time), nullable | When it was last heard from. Null when never. UTC. |
policy | object | The switches — and the screen's orientation — ArdaForm holds for this device and re-states to it on every check-in — only the keys that were ever set, {} when none. A key that is absent means the device keeps what it holds. |
policy.exitAllowed | boolean | Android: whether Shut down — and the app's Arda.system.shutdown() — may close the player. The device's own default is true. False: both are refused on the device, and the portal's Shut down dims. |
policy.kiosk | boolean | Android: lock the screen to the player — no status bar, Home or Recents. Needs the player set up as device owner on the box; a box that is not keeps running as an ordinary app. The device's own default is false. |
policy.orientation | string — portrait or landscape or auto | Which way the screen is drawn: portrait, landscape, or auto — follow the device (its own default). The app's manifest may preset it (screen.orientation) and lock it (screen.writable); a locked orientation is the manifest's whatever is saved here. Android devices act on it today. |
android | object, nullable | Only on GET /v1/devices/{uid}, and only on an Android device: what the device last REPORTED about itself (its sysinfo.android) — kiosk is the screen's live lock state, exitAllowed what it applies now, deviceOwner whether kiosk can work at all, with release, model, appVersion and battery. It differs from policy until the device has checked in. Null on a Linux device; absent in the list. |