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

Пакет телеметрии подписанного агента

POST /s/v1/telemetry

https://agents.tanba-ooh.kz/s/v1/telemetry

Пакет tanba.pop/1 на подписанном канале, для агентов без клиентского сертификата. Тело это JSON с device_id и ts (часы агента, не дальше 300 секунд от часов платформы), подписанный ключом, записанным при регистрации, в заголовке X-Tanba-Signature (ES256, r||s, base64url, по точным байтам тела). Дальше правила те же, что на канале сертификата: batch_seq, prev_sha256, sent_at, events, номера записей seq, хранение тела как получено, повторы и конфликты.

Тело не должно быть больше 1 МБ; с одного устройства принимается 10 пакетов в минуту. Подпись, которая не проходит, дает 401 bad_signature, и ничего не сохраняется.

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

Подпись тела ключом, записанным при регистрации. Сертификат не нужен.

Параметры

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

Тело запроса

application/json

PopDeviceBatch

ИмяТипОбязательныйОписаниеПример
schemastringдаФормат пакета. tanba.pop/1
device_idstring (uuid)Только подписанный канал: устройство, которое шлет пакет; на канале сертификата устройство определяется по сертификату."5c0e8f3a-71d2-4b6e-9a14-d8e2f7b30c59"
tsstring (date-time)Только подписанный канал: часы агента, не дальше 300 секунд от часов платформы."2026-10-14T09:05:00.120Z"
batch_seqintegerдаНомер пакета для этого устройства; занимается один раз.1042
prev_sha256stringSHA-256 тела предыдущего пакета в hex; для первого пусто."9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123"
sent_atstring (date-time)Когда плеер закрыл пакет."2026-10-14T09:05:00.120Z"
eventsarray of objectдаЗаписи пакета по порядку; не больше 1000.
events[].kindstringдаВид записи. Завершенный показ это play; reject файл, который плеер отказался показывать, mode_change смена состояния, tamper нарушенный локальный журнал. play, reject, mode_change, tamper
events[].seqintegerНомер записи для этого плеера, +1 для каждой записи через границы пакетов, с 1; обязателен для play.18
events[].play_idstringИдентификатор показа, уникальный для плеера; символы A-Za-z0-9_-."7c1e9a40b2d35f68"
events[].approval_numberstringНомер показанного согласования; обязателен для play, если не задан slot."TNB-2026-000123"
events[].slotstringМатериал уполномоченного органа: без номера согласования, никогда не тарифицируется, считается отдельно. gov, emergency
events[].media_sha256stringSHA-256 показанного файла, 64 символа hex в нижнем регистре; обязателен для play."9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123"
events[].startstring (date-time)Когда показ начался, RFC 3339 с миллисекундами."2026-10-14T09:04:50.000Z"
events[].endstring (date-time)Когда показ закончился; start не позже него."2026-10-14T09:05:00.000Z"
events[].duration_msintegerВремя на экране в миллисекундах; показ засчитывается от 1000.10000
events[].structurestringКод конструкции, на которой, по мнению плеера, он стоит; другой код получает флаг."ALM-0012"
events[].screenobjectСостояние дисплея во время показа.
events[].screen.powerstringБыл ли дисплей включен. on, off, unknown
events[].atstring (date-time)Когда произошло событие; для reject, mode_change и tamper."2026-10-14T09:00:00.000Z"
events[].codestringПочему файл отклонен; для reject. NO_SIGNATURE, BAD_SIGNATURE, HASH_MISMATCH, EXPIRED_APPROVAL, REVOKED
events[].detailobjectДополнительные сведения записей reject, mode_change и tamper. В записи play может быть src, метка источника программатик-показа: {channel, seller_id, deal_id, imp_id}, где channel слово из строчных латинских букв, seller_id идентификатор организации продающей платформы (seller_id из authorised_sellers), deal_id и imp_id необязательны (A-Za-z0-9._:-, до 64). Метка другой формы игнорируется, показ засчитывается. Метка это указание источника, а не доказательство, и тарификацию не меняет.

Пример

{
  "schema": "tanba.pop/1",
  "batch_seq": 1042,
  "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
  "sent_at": "2026-10-14T09:05:00.120Z",
  "events": [
    {
      "kind": "play",
      "seq": 18,
      "play_id": "7c1e9a40b2d35f68",
      "approval_number": "TNB-2026-000123",
      "media_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
      "start": "2026-10-14T09:04:50.000Z",
      "end": "2026-10-14T09:05:00.000Z",
      "duration_ms": 10000,
      "screen": {
        "power": "on"
      }
    }
  ]
}

Ответы

200 Записано; записи, принятые не безоговорочно, перечислены в `items`.

application/json

PopAnswer

ИмяТипОбязательныйОписаниеПример
batch_seqintegerдаНомер пакета.1042
receivedintegerдаЧисло записей, прочитанных из тела.12
acceptedintegerдаЧисло засчитанных записей, включая записи с флагами.10
duplicatesintegerдаЧисло записей с номером не выше уже принятого для их плеера.0
refusedintegerдаЧисло отклоненных записей.2
flaggedintegerдаЧисло принятых записей с флагом.1
duplicatebooleanдаTrue, если этот же пакет уже получали; повторно ничего не засчитано.false
itemsarray of objectдаТолько записи, принятые не безоговорочно; у чистого пакета список пуст.
items[].indexintegerдаПозиция в events, с 0.3
items[].statusstringдаЧто стало с записью. duplicate, refused, flagged
items[].reasonstringЕсть у отклоненных записей, например time_in_future."time_in_future"
items[].flagsarray of stringЕсть у записей с флагами; флаги перечислены в руководстве по передаче показов.

Пример

{
  "batch_seq": 1042,
  "received": 12,
  "accepted": 10,
  "duplicates": 0,
  "refused": 2,
  "flagged": 1,
  "duplicate": false,
  "items": [
    {
      "index": 3,
      "status": "flagged",
      "flags": [
        "incomplete"
      ]
    },
    {
      "index": 7,
      "status": "refused",
      "reason": "time_in_future"
    }
  ]
}

Ошибки

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

СтатусЗначение
400Тело не JSON (invalid_json) или schema неизвестна (schema_unknown).
401Подпись не проходит (bad_signature), устройство неизвестно или отозвано (device_unauthorized) либо ts вне допустимого диапазона (stale_request).
409Номер batch_seq уже занят пакетом с другим содержимым (batch_conflict) либо пакет в прежнем формате пришел от устройства, у которого уже принят пакет tanba.pop/1 (legacy_after_pop; не повторяйте его).
413Больше 1 МБ или 1000 событий (body_too_large, batch_too_large).
422Ошибки полей (validation_failed).
429Больше одного пакета в 10 секунд для устройства (допустимы 3 подряд). В Retry-After указано, сколько секунд ждать; повторите те же байты (rate_limited).
defaultОшибка в виде документа problem.

Примеры кода

curl

curl -X POST 'https://agents.tanba-ooh.kz/s/v1/telemetry' \
  -H 'X-Tanba-Signature: q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw' \
  -H 'Content-Type: application/json' \
  -d '{
  "schema": "tanba.pop/1",
  "batch_seq": 1042,
  "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
  "sent_at": "2026-10-14T09:05:00.120Z",
  "events": [
    {
      "kind": "play",
      "seq": 18,
      "play_id": "7c1e9a40b2d35f68",
      "approval_number": "TNB-2026-000123",
      "media_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
      "start": "2026-10-14T09:04:50.000Z",
      "end": "2026-10-14T09:05:00.000Z",
      "duration_ms": 10000,
      "screen": {
        "power": "on"
      }
    }
  ]
}'
Python
import requests

response = requests.post(
    "https://agents.tanba-ooh.kz/s/v1/telemetry",
    headers={
        "X-Tanba-Signature": "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw",
        "Content-Type": "application/json",
    },
    json={
        "schema": "tanba.pop/1",
        "batch_seq": 1042,
        "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
        "sent_at": "2026-10-14T09:05:00.120Z",
        "events": [
            {
                "kind": "play",
                "seq": 18,
                "play_id": "7c1e9a40b2d35f68",
                "approval_number": "TNB-2026-000123",
                "media_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
                "start": "2026-10-14T09:04:50.000Z",
                "end": "2026-10-14T09:05:00.000Z",
                "duration_ms": 10000,
                "screen": {
                    "power": "on",
                },
            },
        ],
    },
)
print(response.status_code, response.text)
Go
package main

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

func main() {
	body := strings.NewReader(`{
  "schema": "tanba.pop/1",
  "batch_seq": 1042,
  "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
  "sent_at": "2026-10-14T09:05:00.120Z",
  "events": [
    {
      "kind": "play",
      "seq": 18,
      "play_id": "7c1e9a40b2d35f68",
      "approval_number": "TNB-2026-000123",
      "media_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
      "start": "2026-10-14T09:04:50.000Z",
      "end": "2026-10-14T09:05:00.000Z",
      "duration_ms": 10000,
      "screen": {
        "power": "on"
      }
    }
  ]
}`)
	req, err := http.NewRequest("POST", "https://agents.tanba-ooh.kz/s/v1/telemetry", 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))
}