Отрезок журнала с цепочкой хэшей в ответ на команду 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_id | string (uuid) | да | Команда audit_export, на которую отвечает отрезок. | "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10" |
entries | array of object | да | Строки журнала после прошлой выгрузки по порядку. | |
entries[].seq | integer | да | Номер строки. | 4021 |
entries[].at | string (date-time) | да | Когда записана строка. | "2026-10-14T09:00:00Z" |
entries[].kind | string | да | Вид строки. | "play" |
entries[].data | object | Содержимое строки. | ||
entries[].prev | string | да | Хэш предыдущей строки. | "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123" |
entries[].hash | string | да | Хэш этой строки. | "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
| Имя | Тип | Обязательный | Описание | Пример |
|---|---|---|---|---|
result | string | да | Что показали проверки. ok, chain_broken, continuity_broken, head_mismatch | |
ok | boolean | да | True, если все проверки пройдены. | true |
entries | integer | да | Число строк в отрезке. | 120 |
head_hash | string | да | Хэш последней строки отрезка. | "4d7a1c0e9b3f58a26c4e1d07b9a3f5c8e2d60b14a7c93e5f8d1b2a6c0e4f7391" |
duplicate | boolean | да | 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))
}