TAŇBA-OOH Разработчикам

Отрезок журнала с цепочкой хэшей в ответ на команду audit_export

POST /d/v1/logs

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

Тело (подписано, как телеметрия): {command_id, entries: [{seq, at, kind, data, prev, hash}]}. Платформа проверяет цепочку, то, что отрезок продолжает прошлую выгрузку, и то, что в нем есть хэш головы, названный в последнем heartbeat. В ответе вердикт; отрезок, который не проходит проверку, открывает событие контроля device_log_tampered. На одну команду один ответ; повтор возвращает сохраненный.

Аутентификация

Клиентский сертификат устройства (взаимный TLS) и подпись тела ключом устройства.

Параметры

ИмяГдеТипОбязательныйОписаниеПример
X-Tanba-SignatureзаголовокstringдаПодпись ES256 точных байтов тела ключом устройства, сырые r||s (64 байта), base64url без заполнения.q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw

Тело запроса

application/json

ИмяТипОбязательныйОписаниеПример
command_idstring (uuid)даКоманда audit_export, на которую отвечает отрезок."0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10"
entriesarray of objectдаСтроки журнала после прошлой выгрузки по порядку.
entries[].seqintegerдаНомер строки.4021
entries[].atstring (date-time)даКогда записана строка."2026-10-14T09:00:00Z"
entries[].kindstringдаВид строки."play"
entries[].dataobjectСодержимое строки.
entries[].prevstringдаХэш предыдущей строки."9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123"
entries[].hashstringдаХэш этой строки."4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391"

Пример

{
  "command_id": "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10",
  "entries": [
    {
      "seq": 4021,
      "at": "2026-10-14T09:00:00Z",
      "kind": "play",
      "data": {
        "approval_number": "TNB-2026-000123"
      },
      "prev": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
      "hash": "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391"
    }
  ]
}

Ответы

200 Вердикт.

application/json

ИмяТипОбязательныйОписаниеПример
resultstringдаЧто показали проверки. ok, chain_broken, continuity_broken, head_mismatch
okbooleanдаTrue, если все проверки пройдены.true
entriesintegerдаЧисло строк в отрезке.120
head_hashstringдаХэш последней строки отрезка."4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391"
duplicatebooleanдаTrue, если это повтор и возвращен сохраненный вердикт.false

Пример

{
  "result": "ok",
  "ok": true,
  "entries": 120,
  "head_hash": "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391",
  "duplicate": false
}

Ошибки

Ошибки приходят документом problem (application/problem+json) со стабильным полем code.

СтатусЗначение
401Подпись не проходит либо устройство неизвестно или отозвано.
413Отрезок больше 4 МБ.
defaultОшибка в виде документа problem.

Примеры кода

curl

curl -X POST 'https://devices.tanba-ooh.kz/d/v1/logs' \
  -H 'X-Tanba-Signature: q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw' \
  -H 'Content-Type: application/json' \
  -d '{
  "command_id": "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10",
  "entries": [
    {
      "seq": 4021,
      "at": "2026-10-14T09:00:00Z",
      "kind": "play",
      "data": {
        "approval_number": "TNB-2026-000123"
      },
      "prev": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
      "hash": "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391"
    }
  ]
}'
Python
import requests

response = requests.post(
    "https://devices.tanba-ooh.kz/d/v1/logs",
    headers={
        "X-Tanba-Signature": "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw",
        "Content-Type": "application/json",
    },
    json={
        "command_id": "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10",
        "entries": [
            {
                "seq": 4021,
                "at": "2026-10-14T09:00:00Z",
                "kind": "play",
                "data": {
                    "approval_number": "TNB-2026-000123",
                },
                "prev": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
                "hash": "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391",
            },
        ],
    },
)
print(response.status_code, response.text)
Go
package main

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

func main() {
	body := strings.NewReader(`{
  "command_id": "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10",
  "entries": [
    {
      "seq": 4021,
      "at": "2026-10-14T09:00:00Z",
      "kind": "play",
      "data": {
        "approval_number": "TNB-2026-000123"
      },
      "prev": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
      "hash": "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391"
    }
  ]
}`)
	req, err := http.NewRequest("POST", "https://devices.tanba-ooh.kz/d/v1/logs", 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))
}