TAŇBA-OOH Әзірлеушілерге

Report plays from a CMS, one signed batch for many players

POST /api/v1/pop/batches

https://app.tanba-ooh.kz/api/v1/pop/batches

Анықтамалықтың техникалық мәтіні ағылшын тілінде жазылған.

Scope write:plays. Operator organisations only. A batch is sent with an API key and a signature: the CMS installation is registered once in the console (name, kind and an ECDSA P-256 public key as base64 of its SubjectPublicKeyInfo), each of its players is mapped there to a structure of the organisation, and every batch names the installation in X-Tanba-Source and carries in X-Tanba-Signature the ES256 signature (raw r||s, base64url, no padding) of the exact bytes of the body. The body may be sent with Content-Encoding: gzip; the signature then covers the bytes after decompression.

The body is tanba.pop/1 with records in place of events: schema, batch_seq (a whole number, 0 or more, taken once per installation), prev_sha256 (SHA-256 of the body of batch batch_seq - 1, empty for the first), sent_at and records. A record is a play as described in the guide "Send proof of play from your players or CMS", plus player, the external id the player was mapped under; seq runs on by one for each player across batches. Records of other kinds are refused. At most 10 000 records, 4 MB on the wire and 16 MB after decompression (a compressed body may not inflate to more than 20 times its size).

Records are judged one by one. A record is refused, listed in items and not counted when the player is not mapped (or its mapping was removed) as player_unknown, or for the reasons listed in that guide; the rest of the batch counts. Counts are stored on the mapped player as attested plays: reported by the operator's system and signed by its key, not seen on the device. Same batch_seq with the same bytes is answered duplicate: true and counted once; with other bytes it is refused 409 batch_conflict. Records at or below the highest number already taken for their player are not counted again.

Limits: 2 batches a second for each installation (burst 10), 10 calls a second for the key (burst 30), and 2 000 000 records an hour for the organisation; beyond them the answer is 429 rate_limited with Retry-After. A key that was rotated out is accepted for 24 hours after the new one was registered; after that the answer is 401 source_unauthorized. A signature that does not verify is 401 bad_signature and nothing is stored.

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

Authorization тақырыбындағы API кілті.

Рұқсат: write:plays

Параметрлер

АтауыОрныТүріМіндеттіСипаттамаМысал
X-Tanba-Sourceтақырыпstring (uuid)иәIdentifier of the registered CMS installation that signed the batch.3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836
X-Tanba-SignatureтақырыпstringиәES256 signature of the body (after decompression), raw r||s, base64url without padding.q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw
Content-Encodingтақырыпstringжоқgzip when the body is compressed. gzipgzip

Сұраудың денесі

application/json

АтауыТүріМіндеттіСипаттамаМысал
schemastringиәFormat of the batch. tanba.pop/1
source_idstring (uuid)иәThe source, inside the signed bytes; must equal X-Tanba-Source."3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836"
batch_seqintegerиәNumber of the batch for this installation; taken once.1042
prev_sha256stringHex SHA-256 of the body of the previous batch; empty for the first."9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123"
sent_atstring (date-time)When the CMS cut the batch."2026-10-14T09:05:00.120Z"
recordsarray of objectиәOne entry for each finished play, in the order the players reported them.
records[].kindstringиәOnly plays are taken from a CMS. play
records[].playerstringиәThe external id the player was mapped under in the console."1001:55:2"
records[].seqintegerиәNumber of the record for this player, +1 for every record across batches, from 1.18
records[].play_idstringиәIdentifier of the play, unique for the player; characters A-Za-z0-9_-."7c1e9a40b2d35f68"
records[].approval_numberstringNumber of the approval that was played; required unless slot is set."TNB-2026-000123"
records[].slotstringAuthority content: no approval number, never billed. gov, emergency
records[].media_sha256stringиәSHA-256 of the file played, 64 lowercase hex characters."9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123"
records[].startstring (date-time)иәWhen the play began, RFC 3339."2026-10-14T09:04:50.000Z"
records[].endstring (date-time)иәWhen the play ended, RFC 3339."2026-10-14T09:05:00.000Z"
records[].duration_msintegerиәTime on screen in milliseconds.10000
records[].structurestringCode of the structure the player believes it stands at; a different one is flagged."ALM-0012"

Мысал

{
  "schema": "tanba.pop/1",
  "source_id": "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836",
  "batch_seq": 1042,
  "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
  "sent_at": "2026-10-14T09:05:00.120Z",
  "records": [
    {
      "kind": "play",
      "player": "1001:55:2",
      "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
    }
  ]
}

Жауаптар

200 The batch was read; see `items` for the records that were not plainly accepted.

application/json

АтауыТүріМіндеттіСипаттамаМысал
batch_seqintegerиәNumber of the batch.1042
receivedintegerиәNumber of records read from the body.12
acceptedintegerиәNumber of records counted, flagged ones included.10
duplicatesintegerиәNumber of records at or below the highest number already taken for their player.0
refusedintegerиәNumber of records refused.2
flaggedintegerиәNumber of accepted records that carry a flag.1
duplicatebooleanиәTrue when this very batch was received before; nothing was counted again.false
itemsarray of objectиәOnly the records that are not plainly accepted; a clean batch gets an empty list.
items[].indexintegerиәPosition in records, from 0.3
items[].statusstringиәWhat became of the record. duplicate, refused, flagged
items[].reasonstringPresent on refused records, for example player_unknown or time_in_future."player_unknown"
items[].flagsarray of stringPresent on flagged records; the flags are listed in the guide on sending proof of play.

Мысал

{
  "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": "player_unknown"
    }
  ]
}

Қателер

Қателер problem құжаты (application/problem+json) түрінде, тұрақты code өрісімен келеді.

МәртебесіМағынасы
401No valid session or API key (unauthenticated, session_expired, invalid_api_key).
403The role or the key's scope does not allow this (forbidden, key_scope_denied, key_route_denied).
409The batch_seq was taken by a batch with other content (batch_conflict).
413Body over the size limits (body_too_large, batch_too_large).
415Not JSON, or an encoding other than gzip (unsupported_media_type).
422Field errors (validation_failed).
429Rate limit; see Retry-After.
defaultAn error as a problem document.

Код мысалдары

curl

curl -X POST 'https://app.tanba-ooh.kz/api/v1/pop/batches' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'X-Tanba-Source: 3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836' \
  -H 'X-Tanba-Signature: q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw' \
  -H 'Content-Type: application/json' \
  -d '{
  "schema": "tanba.pop/1",
  "source_id": "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836",
  "batch_seq": 1042,
  "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
  "sent_at": "2026-10-14T09:05:00.120Z",
  "records": [
    {
      "kind": "play",
      "player": "1001:55:2",
      "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
    }
  ]
}'
Python
import requests

response = requests.post(
    "https://app.tanba-ooh.kz/api/v1/pop/batches",
    headers={
        "Authorization": "Bearer YOUR_API_KEY",
        "X-Tanba-Source": "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836",
        "X-Tanba-Signature": "q3J2y0uO8e1m4n7pXc5Zk9d2Wv6Tg0hLsB-aRfN1YjE6uHxPzD3oVtKiC8wMbQe2nSgA4rUl7yFhJ9XcT0dZaw",
        "Content-Type": "application/json",
    },
    json={
        "schema": "tanba.pop/1",
        "source_id": "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836",
        "batch_seq": 1042,
        "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
        "sent_at": "2026-10-14T09:05:00.120Z",
        "records": [
            {
                "kind": "play",
                "player": "1001:55:2",
                "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,
            },
        ],
    },
)
print(response.status_code, response.text)
Go
package main

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

func main() {
	body := strings.NewReader(`{
  "schema": "tanba.pop/1",
  "source_id": "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836",
  "batch_seq": 1042,
  "prev_sha256": "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123",
  "sent_at": "2026-10-14T09:05:00.120Z",
  "records": [
    {
      "kind": "play",
      "player": "1001:55:2",
      "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
    }
  ]
}`)
	req, err := http.NewRequest("POST", "https://app.tanba-ooh.kz/api/v1/pop/batches", body)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
	req.Header.Set("X-Tanba-Source", "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836")
	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))
}