TankTutor.
Back to home

Device API documentation

How to send sensor readings to TankTutor. This is an optional feature, meant for people who already have probes connected.

Overview

If you have sensors — for example a TankTutor-PI or a third-party pH or conductivity meter — you can send measurements to your tank with a plain HTTP request. It is not needed to use TankTutor: you can always enter parameters by hand or through the chat.

Endpoint (the path is relative to the site origin): POST /api/devices/readings, with a JSON body.

Device keys

Every request is authenticated with a device key.

  • Keys are created from the tank page (/my-tanks/[id]), in the "Device keys" panel. An account is required.
  • At most 4 active keys per account, counting all tanks together.
  • A key is bound to a single tank and cannot be moved.
  • The full key (ttk_…) is shown only once, at creation: afterwards only the first 8 characters stay visible. Store it right away.
  • Revoking a key frees its slot immediately. A revoked key gets 401.

Authentication

Send the key in the Authorization: Bearer ttk_YOUR_KEY header (the Bearer scheme is case-insensitive). No session cookie is needed.

Request format

The body is a JSON object with a readings array.

FieldTypeNotes
readingsarrayArray of 1–3 items. A type cannot appear twice.
typestringOne of temperature, ph, conductivity.
valuenumberFinite number.
unitstringOptional, temperature only: "C" or "F" (default "C"). The value is always stored in Celsius; the public badge shows °F only for accounts whose language is English.

If even a single entry is invalid, the whole request is rejected with 400 and nothing is saved.

Example body
{
  "readings": [
    { "type": "temperature", "value": 25.4, "unit": "C" },
    { "type": "ph", "value": 7.2 }
  ]
}

Responses

StatusBodyMeaning
200{"saved": n}Readings saved; n is the number of readings.
400{"error":"invalid_device_readings"}Invalid body (missing field, unknown or repeated type, non-numeric value, unsupported unit). Nothing is saved.
401{"error":"authentication_required"}Missing or malformed header.
401{"error":"invalid_device_key"}Unknown or revoked key.
403{"error":"account_suspended"}The account is suspended.
429{"error":"rate_limited"}You exceeded the rate limit. Nothing is saved.

Limits and reliability

At most one request per time window is accepted per user, with all their keys counted together. The default is 5 minutes; the administrator can change it, so do not rely on a fixed value.

A 429 response saves nothing: retry after the window. The readings of one request are saved together, all or none.

Beta service: no SLA is guaranteed (availability and response times are not binding).

Examples

The addresses in the examples use the origin you are reading this page from.

curl
curl -X POST https://tanktutor.it/api/devices/readings \
  -H "Authorization: Bearer ttk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"readings":[{"type":"temperature","value":25.4,"unit":"C"},{"type":"ph","value":7.2}]}'
Python (requests)
import requests

resp = requests.post(
    "https://tanktutor.it/api/devices/readings",
    headers={"Authorization": "Bearer ttk_YOUR_KEY"},
    json={
        "readings": [
            {"type": "temperature", "value": 25.4, "unit": "C"},
            {"type": "ph", "value": 7.2},
        ]
    },
    timeout=10,
)

if resp.status_code == 200:
    print("saved:", resp.json()["saved"])
elif resp.status_code == 429:
    # Rate limited: retry after the window
    print("rate limited")
else:
    print("error:", resp.status_code, resp.json())
Device API documentation