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. gzip | gzip |
Сұраудың денесі
application/json
| Атауы | Түрі | Міндетті | Сипаттама | Мысал |
|---|---|---|---|---|
schema | string | иә | Format of the batch. tanba.pop/1 | |
source_id | string (uuid) | иә | The source, inside the signed bytes; must equal X-Tanba-Source. | "3f6c1d2a-8b47-4e19-a5d0-7c2e9b14f836" |
batch_seq | integer | иә | Number of the batch for this installation; taken once. | 1042 |
prev_sha256 | string | Hex SHA-256 of the body of the previous batch; empty for the first. | "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123" | |
sent_at | string (date-time) | When the CMS cut the batch. | "2026-10-14T09:05:00.120Z" | |
records | array of object | иә | One entry for each finished play, in the order the players reported them. | |
records[].kind | string | иә | Only plays are taken from a CMS. play | |
records[].player | string | иә | The external id the player was mapped under in the console. | "1001:55:2" |
records[].seq | integer | иә | Number of the record for this player, +1 for every record across batches, from 1. | 18 |
records[].play_id | string | иә | Identifier of the play, unique for the player; characters A-Za-z0-9_-. | "7c1e9a40b2d35f68" |
records[].approval_number | string | Number of the approval that was played; required unless slot is set. | "TNB-2026-000123" | |
records[].slot | string | Authority content: no approval number, never billed. gov, emergency | ||
records[].media_sha256 | string | иә | SHA-256 of the file played, 64 lowercase hex characters. | "9f2b6a41c7d83e05a1b4c6d7e8f90123456789abcdef0123456789abcdef0123" |
records[].start | string (date-time) | иә | When the play began, RFC 3339. | "2026-10-14T09:04:50.000Z" |
records[].end | string (date-time) | иә | When the play ended, RFC 3339. | "2026-10-14T09:05:00.000Z" |
records[].duration_ms | integer | иә | Time on screen in milliseconds. | 10000 |
records[].structure | string | Code 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_seq | integer | иә | Number of the batch. | 1042 |
received | integer | иә | Number of records read from the body. | 12 |
accepted | integer | иә | Number of records counted, flagged ones included. | 10 |
duplicates | integer | иә | Number of records at or below the highest number already taken for their player. | 0 |
refused | integer | иә | Number of records refused. | 2 |
flagged | integer | иә | Number of accepted records that carry a flag. | 1 |
duplicate | boolean | иә | True when this very batch was received before; nothing was counted again. | false |
items | array of object | иә | Only the records that are not plainly accepted; a clean batch gets an empty list. | |
items[].index | integer | иә | Position in records, from 0. | 3 |
items[].status | string | иә | What became of the record. duplicate, refused, flagged | |
items[].reason | string | Present on refused records, for example player_unknown or time_in_future. | "player_unknown" | |
items[].flags | array of string | Present 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 өрісімен келеді.
| Мәртебесі | Мағынасы |
|---|---|
| 401 | No valid session or API key (unauthenticated, session_expired, invalid_api_key). |
| 403 | The role or the key's scope does not allow this (forbidden, key_scope_denied, key_route_denied). |
| 409 | The batch_seq was taken by a batch with other content (batch_conflict). |
| 413 | Body over the size limits (body_too_large, batch_too_large). |
| 415 | Not JSON, or an encoding other than gzip (unsupported_media_type). |
| 422 | Field errors (validation_failed). |
| 429 | Rate limit; see Retry-After. |
| default | An 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))
}