Automation API
Drive check-in, check-out and emergencies from anywhere — and react to bathroom events in real time. Built for n8n, Home Assistant, sensors and plain HTTP.
Overview
The Automation API is the inbound counterpart to Thronfrei's outbound Smart Home webhooks.
Thronfrei sends events when a bathroom becomes occupied or free, or when someone raises an emergency (see Outbound webhooks). The Automation API lets external systems act back on Thronfrei: check a family member in or out, raise emergencies, or read the current status.
Everything is JSON over HTTPS. If you use n8n, the dedicated community node wraps all of this for you.
Base URL
All endpoints live under /v1:
https://automation.thronfrei.de/v1
Requests and responses are JSON. Send content-type: application/json on
requests with a body.
Authentication
Every request except /ping needs a family-scoped API key. Create one in
the Thronfrei app under More → Smart Home. Keys start with
tfa_ and are shown only once.
Authorization: Bearer tfa_xxxxxxxxxxxxxxxx
As a fallback for clients that cannot set Authorization, the header
X-Api-Key: tfa_... is also accepted. The key alone determines the family —
you never pass a family ID.
Acting user
Writes happen in the name of a family member. Choose who with the optional
userId field; if omitted, the key's configured default user is
used. The chosen user must be an active member of the key's family.
{ "userId": "66f8...a12" }
Errors
Failed requests return a consistent shape with the matching HTTP status
(400, 401, 403, 404,
409, 500):
{
"ok": false,
"error": "bathroom_occupied",
"message": "Bad oben ist besetzt."
}
error is a stable machine-readable reason code; message is a
human-friendly explanation.
Endpoints
All bathrooms of the family with occupancy and active
emergencies. Accepts an optional userId to flag the caller's own rooms.
{
"ok": true,
"action": "status",
"familyId": "66f8...f01",
"bathrooms": [
{
"id": "bad_oben",
"name": "Bad oben",
"floor": "OG",
"status": "occupied",
"occupiedByName": "Max",
"hasPeeEmergency": false,
"hasToiletPaperEmergency": false,
"occupiedSince": "2026-07-22T18:20:00.000Z"
}
]
}
A compact bathroom list — ideal to populate a dropdown.
{
"ok": true,
"familyId": "66f8...f01",
"bathrooms": [
{ "id": "bad_oben", "name": "Bad oben", "floor": "OG", "status": "free" }
]
}
Marks the bathroom occupied by the acting user and opens a session.
| Field | Type | Notes | |
|---|---|---|---|
userId | string | optional | Acting member; defaults to the key's default user. |
# request
curl -X POST https://automation.thronfrei.de/v1/bathrooms/bad_oben/check-in \
-H "Authorization: Bearer tfa_..." \
-H "content-type: application/json" \
-d '{"userId":"66f8...a12"}'
# 200
{ "ok": true, "action": "check_in", "bathroomId": "bad_oben", "message": "Bad oben ist jetzt für Max reserviert." }
409 if the bathroom is occupied or the user is already checked in elsewhere.
Ends the acting user's active session and frees the bathroom. Any matching pee emergency is resolved automatically.
| Field | Type | Notes | |
|---|---|---|---|
userId | string | optional | Acting member; defaults to the key's default user. |
{bathroomId} is validated against the user's active session.
409 if the user is not checked in, or is in another bathroom.
Raises a pee emergency for the acting user.
| Field | Type | Notes | |
|---|---|---|---|
userId | string | optional | Acting member. |
bathroomId | string | optional | Target bathroom. |
urgencyLevel | integer | optional | 1–3, default 2. |
message | string | optional | Max 240 chars. |
{ "ok": true, "action": "pee_emergency", "bathroomId": null, "message": "Pipi-Notruf gesendet." }
409 if the requester already has an active emergency.
Raises a toilet-paper emergency. The acting user must currently occupy the target bathroom.
| Field | Type | Notes | |
|---|---|---|---|
userId | string | optional | Acting member. |
bathroomId | string | optional | Defaults to the user's active session. |
403 if the user does not occupy the bathroom · 409 if the bathroom is free or the emergency is already active.
Outbound webhooks
Configure targets in the app under More → Smart Home. Thronfrei
POSTs a compact JSON payload to each enabled target when a matching event
occurs. No internal IDs, tokens or secrets are ever sent.
{
"app": "Thronfrei",
"event": "pipi_notruf",
"title": "Pipi-Notruf",
"message": "Max wartet auf Bad oben.",
"bathroom": "Bad oben",
"person": "Max",
"occurredAt": "2026-07-22T18:20:00.000Z"
}
Event names
bad_besetzt
A bathroom became occupied.
bad_frei
A bathroom became free.
klopapier_notruf
Toilet-paper emergency raised.
klopapier_erledigt
Toilet-paper emergency resolved.
pipi_notruf
Pee emergency raised.
pipi_gesehen
Pee emergency acknowledged.
pipi_erledigt
Pee emergency resolved.
pipi_abgebrochen
Pee emergency cancelled.
Targets support GET/POST/PUT/PATCH and WebSocket
(ws:// local, wss:// remote), a payload template with
{{placeholders}}, and plain HTTP for private LAN devices (e.g. a Shelly plug).
Signature verification
If a target has a secret, Thronfrei signs the exact request body and sends it as an HMAC-SHA256 header. Verify it to be sure a request really came from Thronfrei:
x-thronfrei-signature: sha256=<hex>
x-thronfrei-event: pipi_notruf
// Node.js
const expected = 'sha256=' +
crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
Sign over the raw request body bytes, not a re-serialized object.
n8n node
The community package n8n-nodes-thronfrei ships two nodes:
Thronfrei Trigger
A webhook receiver. Copy its Production URL into a Smart Home target, pick your events, and optionally verify the HMAC signature.
Thronfrei
Actions for check-in, check-out, status and emergencies. The bathroom dropdown is filled live from your family.
Install via Settings → Community Nodes and search for
n8n-nodes-thronfrei. Add a Thronfrei API credential with your
base URL and tfa_ key.
Source code, issues and releases: github.com/pandee-de/n8n-nodes-thronfrei.
OpenAPI spec
The full API is described as an OpenAPI 3.1 document. Import it into Postman, Insomnia, or n8n's HTTP Request node, or generate a client: