TAŇBA-OOH Developers

Device heartbeat (client certificate, signed body)

POST /d/v1/heartbeat

https://devices.tanba-ooh.kz/d/v1/heartbeat

The player reports its state and receives commands. The body is signed with the device key (header X-Tanba-Signature, ES256, r||s, base64url) and carries a rising seq and the nonce given in the last answer. With wait (up to 25 seconds) the platform holds the answer until a command is ready, so a cut-off or a restore reaches a connected player within seconds. A player that makes presence tokens for its QR code sends qr_key: false until it holds its key, true after; while it is false the answer carries qr_secret and returns at once.

A clock difference above five minutes is recorded as a clock skew. A change of edid_sha256 is recorded as a changed screen. A display size (screen_width_px and screen_height_px) that differs from the size on file for the structure in every heartbeat for 24 hours is recorded as a screen size mismatch; a heartbeat without a size changes nothing, and a change of the network prefix of the caller (/24 for IPv4, /48 for IPv6) as a changed network. A silence longer than the grace of the class (10 minutes for class A and B, 60 for C) is stored as an offline interval when the player is heard again.

Authentication

Client certificate of the device (mutual TLS) and a signature of the body by the device key.

Parameters

NameInTypeRequiredDescriptionExample
X-Tanba-SignatureheaderstringyesES256 signature of the exact bytes of the body by the device key, raw r||s (64 bytes), base64url without padding.q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw
waitqueryintegernoSeconds to hold the answer until a command is ready; 0 to 25.25

Request body

application/json

NameTypeRequiredDescriptionExample
seqintegeryesNumber of the heartbeat; it must be above the last one taken.1840
statestringyesState of the player. normal, compliance, cutoff
noncestringyesThe nonce or next_nonce of the last answer."3b9f0c6e2a7d41f85e0b9a3c7d2e1f60"
firmwarestringFirmware version."4.2.1"
spec_versionstringVersion of the conformance specification."1.1"
log_head_hashstringHex SHA-256 of the newest line of the hash-chained local log."9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123"
tsstring (date-time)The clock of the player."2026-10-14T09:05:00Z"
current_approvalstringNumber of the approval on screen now."TNB-2026-000123"
revocation_list_versionintegerVersion of the revocation list in use.42
edid_sha256stringSHA-256 of the EDID block of the display, in hex."4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391"
screen_width_pxintegerWidth in pixels of the display as the player sees it. Send with screen_height_px or not at all; a pair that differs from the size on file for the structure for 24 hours is recorded as a screen size mismatch.1920
screen_height_pxintegerHeight in pixels of the display as the player sees it; see screen_width_px.1080
qr_keybooleanFalse until the player holds its presence key, true after.true

Example

{
  "seq": 1840,
  "state": "normal",
  "nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
  "firmware": "4.2.1",
  "spec_version": "1.1",
  "ts": "2026-10-14T09:05:00Z",
  "current_approval": "TNB-2026-000123",
  "revocation_list_version": 42,
  "qr_key": true
}

Responses

200 Accepted.

application/json

NameTypeRequiredDescriptionExample
server_timestring (date-time)yesThe clock of the platform."2026-10-14T09:05:00Z"
next_noncestringyesThe nonce for the next heartbeat."c4e7a19b0d2f63858a1e9c7b3d05f246"
commandsarray of stringyesSigned command envelopes addressed to this player, base64 of the DER form; at most the oldest few not yet acknowledged.
revocation_listobject | nullThe newest revocation list: its version and address.
revocation_list.versionintegerVersion of the list.42
revocation_list.urlstringAddress of the list."https://devices.tanba-ooh.kz/d/v1/revocations/42"
telemetry_cut_sintegeryesSeconds after which the player cuts a telemetry batch; it also cuts at 1000 events.300
qr_secretstring64 hex characters, present only while the player reports qr_key false."7b1d4e90a2c65f38e0b7a91d3c5f8264b0e9d1a7c3f5826e4b0d9a1c7e3f5824"

Example

{
  "server_time": "2026-10-14T09:05:00Z",
  "next_nonce": "c4e7a19b0d2f63858a1e9c7b3d05f246",
  "commands": [
    "MIIC0QYJKoZIhvcNAQcCoIICwjCCAr4CAQExDzANBglghkgBZQMEAgEFADA"
  ],
  "revocation_list": {
    "version": 42,
    "url": "https://devices.tanba-ooh.kz/d/v1/revocations/42"
  },
  "telemetry_cut_s": 300,
  "qr_secret": "7b1d4e90a2c65f38e0b7a91d3c5f8264b0e9d1a7c3f5826e4b0d9a1c7e3f5824"
}

Errors

Errors are problem documents (application/problem+json) with a stable code.

StatusMeaning
401The signature does not verify (bad_signature), or the device is unknown, revoked or expired (device_unauthorized).
409The seq is not above the last one (replay) or the nonce is not the one issued (nonce_mismatch; the problem carries next_nonce).
422Field errors (validation_failed).
defaultAn error as a problem document.

Code samples

curl

curl -X POST 'https://devices.tanba-ooh.kz/d/v1/heartbeat' \
  -H 'X-Tanba-Signature: q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw' \
  -H 'Content-Type: application/json' \
  -d '{
  "seq": 1840,
  "state": "normal",
  "nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
  "firmware": "4.2.1",
  "spec_version": "1.1",
  "ts": "2026-10-14T09:05:00Z",
  "current_approval": "TNB-2026-000123",
  "revocation_list_version": 42,
  "qr_key": true
}'
Python
import requests

response = requests.post(
    "https://devices.tanba-ooh.kz/d/v1/heartbeat",
    headers={
        "X-Tanba-Signature": "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw",
        "Content-Type": "application/json",
    },
    json={
        "seq": 1840,
        "state": "normal",
        "nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
        "firmware": "4.2.1",
        "spec_version": "1.1",
        "ts": "2026-10-14T09:05:00Z",
        "current_approval": "TNB-2026-000123",
        "revocation_list_version": 42,
        "qr_key": True,
    },
)
print(response.status_code, response.text)
Go
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "seq": 1840,
  "state": "normal",
  "nonce": "3b9f0c6e2a7d41f85e0b9a3c7d2e1f60",
  "firmware": "4.2.1",
  "spec_version": "1.1",
  "ts": "2026-10-14T09:05:00Z",
  "current_approval": "TNB-2026-000123",
  "revocation_list_version": 42,
  "qr_key": true
}`)
	req, err := http.NewRequest("POST", "https://devices.tanba-ooh.kz/d/v1/heartbeat", body)
	if err != nil {
		panic(err)
	}
	req.Header.Set("X-Tanba-Signature", "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw")
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Status, string(data))
}