Two transports expose the same status structure (built by StatusJson), so
a client can use whichever it prefers. All HTTP bodies are JSON.
Base URL is the device IP (or http://openember.local/ if mDNS resolves).
GET /api/status → full snapshot:
{
"fw": "0.1.0",
"uptimeS": 4210,
"grill": {
"state": "RUNNING", "tempF": 224.3, "setpointF": 225,
"augerOn": true, "augerDuty": 0.22, "igniterOn": false, "fanOn": true,
"superSmoke": false, "lit": true, "secondsInState": 3600
},
"probes": [
{ "index": 0, "connected": true, "tempF": 149.8 },
{ "index": 1, "connected": false, "tempF": null }
],
"cook": {
"state": "RUNNING", "recipe": "Smoked Brisket",
"stepIndex": 1, "stepCount": 4, "stepName": "Cook to the stall",
"stepSetpointF": 225, "stepSuperSmoke": false,
"advanceType": "probe", "probeIndex": 0, "probeTargetF": 165,
"stepProgress": 0.9, "stepElapsedS": 5400, "stepRemainingS": 0,
"percentComplete": 25
},
"net": { "mode": "STA", "connected": true, "ip": "192.168.1.42" }
}A null temperature means the sensor is not present or is faulted — never treat
it as a number.
| Method & path | Body | Effect |
|---|---|---|
POST /api/grill/setpoint |
{"tempF": 225} |
Set target temp (rejected 409 while a cook is RUNNING). |
POST /api/grill/supersmoke |
{"on": true} |
Toggle Super Smoke (rejected 409 while a cook is RUNNING). |
POST /api/grill/start |
— | Begin the ignition sequence. |
POST /api/grill/shutdown |
— | Controlled shutdown + cool-down. |
POST /api/grill/clear |
— | Clear a latched ERROR (409 until cool & sensor valid). |
| Method & path | Body | Effect |
|---|---|---|
GET /api/recipes |
— | List {id,name,stepCount}. |
GET /api/recipes?id=<id> |
— | Full recipe JSON. |
POST /api/recipes |
recipe JSON | Create/replace a recipe. |
POST /api/recipes/delete |
{"id":"<id>"} |
Delete. |
| Method & path | Body | Effect |
|---|---|---|
POST /api/cook/load |
{"id":"<id>"} |
Stage a recipe. |
POST /api/cook/begin |
— | Begin Cook. |
POST /api/cook/stop |
— | Stop → PAUSED, grill back to manual. |
POST /api/cook/resume |
— | Resume from the saved step. |
POST /api/cook/next |
— | Advance a manual step / skip. |
POST /api/cook/cancel |
— | Abandon the cook and shut down. |
GET /api/config (secrets redacted) · POST /api/config with any subset of:
hostname, wifiSsid, wifiPass, kp, ki, kd, grillOffsetF, mqttHost, mqttPort, mqttUser, mqttPass, haDiscovery. PID gains and the grill offset apply live;
network changes apply on reboot.
Enabled when mqttHost is set. Base topic = hostname (default openember).
| Topic | Dir | Payload |
|---|---|---|
<base>/state |
pub (retained) | same JSON as GET /api/status |
<base>/availability |
pub (LWT) | online / offline |
<base>/cmd/setpoint |
sub | temperature °F, e.g. 225 |
<base>/cmd/supersmoke |
sub | on / off |
<base>/cmd/power |
sub | start / shutdown / clear |
<base>/cmd/cook |
sub | a recipe id (load+begin), or stop/resume/next/cancel |
With haDiscovery: true the device publishes Home Assistant MQTT-discovery
configs for grill temperature, grill state, each meat probe, the setpoint
(as a number) and Super Smoke (as a switch) — so the grill appears in Home
Assistant automatically with no YAML. This is the recommended way to get an app
experience (dashboards, notifications, automations) without building a phone app.