Передача показов из собственной CMS оператора
POST /api/v1/plays/import
https://app.tanba-ooh.kz/api/v1/plays/import
Право write:plays. Только организации-операторы; сессия консоли администратора оператора тоже подходит. Тело это JSON (application/json, объект с массивом rows) или CSV (text/csv, UTF-8, запятая или точка с запятой, строка заголовков), не больше 5 МБ или 50 000 строк.
Каждая строка называет конструкцию вашей организации (structure_id или structure_code, но не оба), номер согласования, целый час и число показов в этот час. source_ref необязателен и называет источник (например, CMS); он входит в идентичность строки. Часы в формате ISO 8601; час без смещения читается как время Asia/Almaty.
Строка хранится под ключом (организация, конструкция, согласование, час, источник). Повторная отправка заменяет прежнее значение и никогда не прибавляется к нему; строки с разными source_ref хранятся отдельно и складываются. Строки оцениваются по одной: в ответе каждая строка помечена как accepted или refused с кодом причины, а принятые строки сохраняются, даже если другие отклонены. Причины:
| Причина | Значение |
|---|---|
structure_missing, structure_ambiguous | Не указано ни structure_id, ни structure_code, либо указаны оба. |
structure_not_found | В вашей организации нет такой конструкции. |
approval_missing, approval_not_found | Нет номера согласования или нет подписанного согласования с таким номером. |
approval_not_for_structure | Согласование не покрывает эту конструкцию. |
approval_not_valid_at_hour | Час вне срока согласования, до его подписания или после отзыва. |
hour_invalid, hour_not_whole | Не час в формате ISO 8601 или не ровно на час. |
hour_in_future, hour_too_old | Час еще не начался или начался больше 62 дней назад. |
plays_invalid | Не целое число, не меньше 0. |
plays_over_cap | Больше 3600 показов в час на каждый экран конструкции (не меньше одного). |
source_ref_invalid | Длиннее 100 символов. |
duplicate_row | Такая же строка уже была в этом пакете. |
Импортированные показы не служат доказательством. Они хранятся отдельно от показов, о которых сообщают плееры, никогда не тарифицируются и никогда не определяют событие контроля. Каждую ночь они сравниваются с отчетными показами (см. reconciliation в /api/v1/stats/reports); если у конструкции, чьи плееры отчитались, импорт расходится с ними больше чем на 10 %, открывается городское событие import_mismatch. Сделать импортированные показы тарифицируемыми можно только отдельным решением платформы, этот интерфейс такого не делает.
Аутентификация
Ключ API в заголовке Authorization или сессия консоли.
Право доступа: write:plays
Тело запроса
application/json
| Имя | Тип | Обязательный | Описание | Пример |
|---|---|---|---|---|
rows | array of object | да | Строки для хранения: по одной на конструкцию, согласование и час. | |
rows[].structure_id | string (uuid) | Идентификатор конструкции; укажите его или structure_code, но не оба. | "b7e4a2c9-0d1f-4a68-8e35-91c7f2d40b16" | |
rows[].structure_code | string | Код конструкции; укажите его или structure_id, но не оба. | "ALM-0012" | |
rows[].approval_number | string | Номер согласования, которое действовало в этот час. | "TNB-2026-000123" | |
rows[].hour | string | ISO 8601, ровно на час. | "2026-10-06T14:00:00+05:00" | |
rows[].plays | integer | Число показов в этот час, целое число от 0. | 118 | |
rows[].source_ref | string | Имя источника, из которого пришла строка, до 100 символов; входит в идентичность строки. | "cms-main" |
Пример
{
"rows": [
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T14:00:00+05:00",
"plays": 118,
"source_ref": "cms-main"
},
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T15:00:00+05:00",
"plays": 120,
"source_ref": "cms-main"
}
]
}
text/csv
Пример
structure_code,approval_number,hour,plays,source_ref
ALM-0012,TNB-2026-000123,2026-10-06T14:00:00+05:00,118,cms-main
ALM-0012,TNB-2026-000123,2026-10-06T15:00:00+05:00,120,cms-main
Ответы
200 Пакет прочитан; что сохранено, видно по строкам.
application/json
| Имя | Тип | Обязательный | Описание | Пример |
|---|---|---|---|---|
batch_id | string (uuid) | да | Идентификатор пакета. | "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10" |
received | integer | да | Число строк, прочитанных из тела. | 2 |
accepted | integer | да | Число сохраненных строк. | 1 |
refused | integer | да | Число отклоненных строк. | 1 |
rows | array of object | да | Запись на каждую строку, в порядке тела. | |
rows[].row | integer | да | Позиция в теле, с 1. | 1 |
rows[].status | string | да | Сохранена ли строка. accepted, refused | |
rows[].reason | string | Есть у отклоненных строк. | "hour_in_future" |
Пример
{
"batch_id": "0b1c3a52-6f0e-4c2e-9d3a-2f5d1e7a9c10",
"received": 2,
"accepted": 1,
"refused": 1,
"rows": [
{
"row": 1,
"status": "accepted"
},
{
"row": 2,
"status": "refused",
"reason": "hour_in_future"
}
]
}
Ошибки
Ошибки приходят документом problem (application/problem+json) со стабильным полем code.
| Статус | Значение |
|---|---|
| 400 | Ошибка в виде документа problem. |
| 401 | Нет действительной сессии или ключа API (unauthenticated, session_expired, invalid_api_key). |
| 403 | Роль или право ключа этого не допускает (forbidden, key_scope_denied, key_route_denied). |
| 413 | Ошибка в виде документа problem. |
| 415 | Ошибка в виде документа problem. |
| 422 | Ошибки полей (validation_failed). |
| default | Ошибка в виде документа problem. |
Примеры кода
curl
curl -X POST 'https://app.tanba-ooh.kz/api/v1/plays/import' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"rows": [
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T14:00:00+05:00",
"plays": 118,
"source_ref": "cms-main"
},
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T15:00:00+05:00",
"plays": 120,
"source_ref": "cms-main"
}
]
}'
Python
import requests
response = requests.post(
"https://app.tanba-ooh.kz/api/v1/plays/import",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"rows": [
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T14:00:00+05:00",
"plays": 118,
"source_ref": "cms-main",
},
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T15:00:00+05:00",
"plays": 120,
"source_ref": "cms-main",
},
],
},
)
print(response.status_code, response.text)
Go
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"rows": [
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T14:00:00+05:00",
"plays": 118,
"source_ref": "cms-main"
},
{
"structure_code": "ALM-0012",
"approval_number": "TNB-2026-000123",
"hour": "2026-10-06T15:00:00+05:00",
"plays": 120,
"source_ref": "cms-main"
}
]
}`)
req, err := http.NewRequest("POST", "https://app.tanba-ooh.kz/api/v1/plays/import", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
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))
}