| title | HTTP API |
|---|---|
| nav_order | 5 |
The server is a Ktor (Netty) app that binds 0.0.0.0:8081
(server/src/main/kotlin/com/arganaemre/mdmcore/Application.kt).
- Base URL —
http://localhost:8081from the host machine (the desktop/web console);http://10.0.2.2:8081from an Android emulator (the agent). See agent.md. - Content type —
application/json, encoded/decoded with the sharedMdmJsonconfig (ignoreUnknownKeys,encodeDefaults; see protocol.md). - CORS —
anyHost()withGET/POST/PUTand theContent-Typeheader allowed. This is dev-only so the browser web console (a different origin) can call the API; lock it down before any real deployment. - Bodies use wrapped ids.
DeviceIdserializes as{"value":"device-1"}. The{id}in a URL path is the bare string (device-1); in a JSON body it's the object. (See the note onDeviceIdin protocol.md.)
| Method | Path | Request body | Success | Errors | Caller |
|---|---|---|---|---|---|
GET |
/ |
— | 200 text |
— | health check |
POST |
/enroll |
EnrollRequest |
200 EnrollResponse |
— | agent |
GET |
/devices/{id}/policy |
— | 200 DevicePolicy |
404 unknown id |
agent |
PUT |
/devices/{id}/policy |
PolicyUpdate |
200 DevicePolicy |
404 unknown id |
console |
POST |
/devices/{id}/token |
TokenRegistration |
202 |
404 unknown id |
agent |
POST |
/devices/{id}/report |
ComplianceReport |
202 |
— | agent |
GET |
/devices |
— | 200 List<DeviceStatus> |
— | console |
GET |
/devices/{id}/status |
— | 200 DeviceStatus |
404 unknown id |
console |
Returns a plain-text greeting ("Hello, Ktor!"). Handy for "is the server up?".
First contact. The server assigns a fresh DeviceId (device-1, device-2, …) and returns it with
the default starting policy (version: 1, cameraDisabled: true).
Request:
{ "model": "Pixel 7", "osVersion": "Android 34", "agentVersion": "1.0" }Response 200:
{ "deviceId": { "value": "device-1" }, "policy": { "version": 1, "cameraDisabled": true } }The agent pulls its desired policy. 404 if the id was never enrolled (the agent treats a 404 as
"the server forgot me" and re-enrolls).
Response 200:
{ "version": 2, "cameraDisabled": false }The console edits desired policy. The body is a PolicyUpdate (editable fields only). The server
bumps the version and returns the new authoritative DevicePolicy. As a side effect it rings
the device's FCM doorbell if a token is registered, so the device re-syncs promptly (best-effort;
a no-op if there's no token or FCM isn't configured — see fcm-doorbell.md).
404 if the id isn't enrolled.
Request:
{ "cameraDisabled": false }Response 200 (note the bumped version):
{ "version": 3, "cameraDisabled": false }Registers/refreshes the device's FCM token. 202 Accepted on success; 404 if the id isn't
enrolled (the server never stores a token for an unknown device).
Request:
{ "token": "e1Hf...registration-token...9Qx" }The agent reports observed state. Always 202 Accepted.
The
{id}in the path is ignored here. The handler keys on thedeviceIdinside the body, not the path segment. Send the correct id in the body; the path is there only for URL symmetry. The endpoint also does not check that the device is enrolled — it stores whatever it receives.
Request:
{
"deviceId": { "value": "device-1" },
"appliedPolicyVersion": 2,
"cameraDisabled": false,
"compliant": true
}The server stores this and recomputes compliance itself on the next read; the compliant field is
not trusted (see the loop).
The console's roster: every enrolled device's status, sorted by id for a stable view.
Response 200:
[
{
"deviceId": { "value": "device-1" },
"desired": { "version": 2, "cameraDisabled": false },
"lastReport": {
"deviceId": { "value": "device-1" },
"appliedPolicyVersion": 1,
"cameraDisabled": true,
"compliant": true
},
"state": "STALE"
}
](state is STALE here because the last report applied version 1 while desired is now version 2.)
One device's DeviceStatus. 404 if the id isn't enrolled. lastReport is null until the device
has reported at least once (then state is UNKNOWN).
Assuming the server is running (./gradlew :server:run) on the host:
# enroll, and capture the assigned id
curl -s -X POST localhost:8081/enroll \
-H 'Content-Type: application/json' \
-d '{"model":"Pixel 7","osVersion":"Android 34","agentVersion":"1.0"}'
# -> {"deviceId":{"value":"device-1"},"policy":{"version":1,"cameraDisabled":true}}
# read the desired policy
curl -s localhost:8081/devices/device-1/policy
# edit the desired policy (bumps version, rings the doorbell if a token is registered)
curl -s -X PUT localhost:8081/devices/device-1/policy \
-H 'Content-Type: application/json' \
-d '{"cameraDisabled":false}'
# report observed state (deviceId is taken from the body, not the path)
curl -s -X POST localhost:8081/devices/device-1/report \
-H 'Content-Type: application/json' \
-d '{"deviceId":{"value":"device-1"},"appliedPolicyVersion":2,"cameraDisabled":false,"compliant":true}'
# the authoritative status
curl -s localhost:8081/devices/device-1/status
curl -s localhost:8081/devicesA full enroll → edit → report → see-it-go-compliant walkthrough is in development.md.