Integration API v1

Base URL

https://api.retailsolution.ai

Локально: http://localhost:8009

Prefix

/integration/v1

Swagger

/docs → tag integration-v1

Правила авторизации

Два типа токена. Scope по умолчанию: analytics, catalog. visitors добавляется отдельно при создании ключа.

ТокенКак получитьГде используется
Интеграционный JWTPOST /integration/v1/token/me, каталог, аналитика, посетители/пользователи
API-ключ cv_live_… / cv_test_…POST /integration/v1/api-keysТак же, как JWT (Bearer)
Dashboard JWT (POST /token)Логин сотрудникаТолько CRUD API-ключей

Staff JWT на аналитике/каталоге → 401 Staff tokens are not accepted on the integration API

Доступные API

#MethodEndpointКтоScope
1POST/integration/v1/tokenпубличный
2GET/integration/v1/meпартнёрany
3POST/integration/v1/api-keysадмин компании
4GET/integration/v1/api-keysадмин компании
5DELETE/integration/v1/api-keys/{key_id}админ компании
6GET/integration/v1/branchesпартнёрcatalog
7GET/integration/v1/buildingsпартнёрcatalog
8GET/integration/v1/camerasпартнёрcatalog
9GET/integration/v1/analytics/branch-summaryпартнёрanalytics
10GET/integration/v1/analytics/branch-summary/export.xlsxпартнёрanalytics
11GET/integration/v1/analytics/hourlyпартнёрanalytics
12GET/integration/v1/analytics/trafficпартнёрanalytics
13GET/integration/v1/visitorsпартнёрvisitors
14GET/integration/v1/usersпартнёрvisitors

Нет: создание интеграционного пользователя / сброс пароля API.

POST/integration/v1/token

Пароль → JWT (1 час). Только пользователь is_integration_user=true. Не JSON body — form-urlencoded.

Request
1
POST /integration/v1/token
2
Content-Type: application/x-www-form-urlencoded
3
4
username=yuksalish_api&password=SECRET

Ответ 200

JSON
1
{
2
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
3
"token_type": "bearer",
4
"expires_in": 3600
5
}
StatusBody
401{ "detail": "Invalid credentials" }
403{ "detail": "Not an integration account" }
400Inactive user или нет компании
429retry_after + заголовок Retry-After

GET/integration/v1/me

Текущий principal и компания. JWT или API-ключ.

Request
1
GET /integration/v1/me
2
Authorization: Bearer eyJ...

Ответ 200 (JWT)

JSON
1
{
2
"user_id": 88,
3
"username": "yuksalish_api",
4
"email": "api@yuksalish.uz",
5
"first_name": "API",
6
"last_name": "User",
7
"company": {
8
"id": 3,
9
"name": "Yuksalish",
10
"description": "Retail",
11
"type": null,
12
"phone": "+99890...",
13
"email": "info@yuksalish.uz",
14
"logo": "https://s3.example/presigned..."
15
}
16
}

Ответ 200 (API-ключ)

user_id равен 0, username равен api_key:{id}.

JSON
1
{
2
"user_id": 0,
3
"username": "api_key:12",
4
"email": null,
5
"first_name": null,
6
"last_name": null,
7
"company": { "id": 3, "name": "Yuksalish" }
8
}
StatusПричина
401Нет токена / неверный / staff JWT
404Компания не найдена

POST/integration/v1/api-keys

Создание ключа. Dashboard JWT. is_admin или владелец компании (или superuser + company_id). api_key приходит только в этом ответе — показать один раз в UI и скопировать.

Request
1
POST /integration/v1/api-keys
2
Authorization: Bearer <dashboard_jwt>
3
Content-Type: application/json
JSON
1
{
2
"name": "Yuksalish ERP",
3
"scopes": ["analytics", "catalog"],
4
"environment": "live"
5
}
ПолеОбязательноПо умолчанию
nameда
scopesнетРазрешено: analytics, catalog, visitors
environmentнетlive → cv_live_, test → cv_test_
JSON
1
{
2
"id": 12,
3
"name": "Yuksalish ERP",
4
"key_prefix": "cv_live_a1b2",
5
"scopes": ["analytics", "catalog"],
6
"is_active": true,
7
"created_at": "2026-08-19T10:15:00",
8
"last_used_at": null,
9
"api_key": "cv_live_a1b2c3d4e5f6...."
10
}

GET/integration/v1/api-keys

Полный secret не возвращается. is_active: false = отозван.

Request
1
GET /integration/v1/api-keys
2
Authorization: Bearer <dashboard_jwt>
JSON
1
[
2
{
3
"id": 12,
4
"name": "Yuksalish ERP",
5
"key_prefix": "cv_live_a1b2",
6
"scopes": ["analytics", "catalog"],
7
"is_active": true,
8
"created_at": "2026-08-19T10:15:00",
9
"last_used_at": "2026-08-19T12:00:00"
10
}
11
]

DELETE/integration/v1/api-keys/{key_id}

Request
1
DELETE /integration/v1/api-keys/12
2
Authorization: Bearer <dashboard_jwt>
JSON 404
1
{
2
"detail": "API key not found"
3
}

Ответ 204 — без body. 404: { "detail": "API key not found" }

JSON
1
{
2
"detail": "API key not found"
3
}

GET/integration/v1/branches

Нет scope → 403 API key or token is missing scope: catalog.

Request
1
GET /integration/v1/branches
2
Authorization: Bearer <integration_jwt | cv_live_>
JSON
1
[
2
{
3
"id": 4,
4
"branch_title": "Chilonzor",
5
"building_id": 10,
6
"timezone": "Asia/Tashkent",
7
"company_id": 3
8
}
9
]

GET/integration/v1/buildings

Request
1
GET /integration/v1/buildings
2
Authorization: Bearer <...>
JSON
1
[
2
{
3
"id": 10,
4
"name": "Chilonzor store",
5
"address": "Toshkent, Chilonzor",
6
"latitude": 41.28,
7
"longitude": 69.21,
8
"zip_code": "100115"
9
}
10
]

GET/integration/v1/cameras

Query building_id необязателен. Не возвращается: password, login, ddns_rtsp_url, embedding.

Request
1
GET /integration/v1/cameras
2
GET /integration/v1/cameras?building_id=10
JSON
1
[
2
{
3
"id": 101,
4
"name": "Kirish",
5
"device_id": "cam-001",
6
"device_ip": "192.168.1.***",
7
"building_id": 10,
8
"room_id": 3,
9
"camera_type": "entry",
10
"is_traffic_camera": false,
11
"calibration_active": true
12
}
13
]

GET/integration/v1/analytics/branch-summary

Дневной отчёт филиала из Excel (JSON). conversion_rate = visit_count / traffic_count (4 знака). Трафик 0 → null.

QueryОбязательноКомментарий
start_dateдаYYYY-MM-DD, inclusive
end_dateдаinclusive, максимум 31 дней
branch_idнетОдин филиал
include_empty_branchesнетПо умолчанию false
Request
1
GET /integration/v1/analytics/branch-summary?start_date=2026-08-17&end_date=2026-08-17
2
GET /integration/v1/analytics/branch-summary?start_date=2026-08-01&end_date=2026-08-17&branch_id=4&include_empty_branches=false
JSON
1
{
2
"start_date": "2026-08-17",
3
"end_date": "2026-08-17",
4
"branches": [
5
{
6
"branch_id": 4,
7
"branch_name": "Chilonzor",
8
"traffic_count": 1200,
9
"conversion_rate": 0.35,
10
"visit_count": 420,
11
"visit_share_pct": 58.33,
12
"gender": { "male_count": 200, "male_pct": 47.62, "female_count": 220, "female_pct": 52.38 },
13
"age": { "under_18": 10, "age_18_30": 180, "age_31_40": 140, "age_41_54": 70, "age_55_plus": 20 },
14
"peak_hour": { "label": "20:00-21:00", "count": 80 },
15
"low_hour": { "label": "08:00-09:00", "count": 5 }
16
}
17
],
18
"totals": { "branch_name": "Total", "traffic_count": 1200, "visit_count": 420 }
19
}
Statusdetail
400start_date должен быть <= end_date / диапазон не больше 31 дня
404Филиал не найден
403нет scope analytics
429лимит 30/час

GET/integration/v1/analytics/branch-summary/export.xlsx

Тот же summary, файл Excel. Лимит: 5/hour. Frontend: responseType: 'blob', затем скачивание. lang = uz | en | ru (по умолчанию uz).

Request
1
GET /integration/v1/analytics/branch-summary/export.xlsx?start_date=2026-08-17&end_date=2026-08-17&lang=uz
2
Authorization: Bearer <...>

Content-Type: spreadsheetml.sheet. Body: binary XLSX, не JSON.

GET/integration/v1/analytics/hourly

Один филиал, один день, 24 часа. Имя query — date. Оба обязательны. В hours всегда 24 элемента.

Request
1
GET /integration/v1/analytics/hourly?branch_id=4&date=2026-08-17
JSON
1
{
2
"branch_id": 4,
3
"branch_name": "Chilonzor",
4
"date": "2026-08-17",
5
"timezone": "Asia/Tashkent",
6
"hours": [
7
{ "label": "00:00-01:00", "count": 0 },
8
{ "label": "20:00-21:00", "count": 80 },
9
{ "label": "23:00-00:00", "count": 4 }
10
]
11
}

GET/integration/v1/analytics/traffic

Только уличный поток (traffic_count). Даты обязательны, максимум 31 день, branch_id необязателен.

Request
1
GET /integration/v1/analytics/traffic?start_date=2026-08-17&end_date=2026-08-17
2
GET /integration/v1/analytics/traffic?start_date=2026-08-17&end_date=2026-08-17&branch_id=4
JSON
1
{
2
"start_date": "2026-08-17",
3
"end_date": "2026-08-17",
4
"branches": [
5
{ "branch_id": 4, "branch_name": "Chilonzor", "traffic_count": 1200 },
6
{ "branch_id": 5, "branch_name": "Yunusobod", "traffic_count": 800 }
7
],
8
"total_traffic_count": 2000
9
}

GET/integration/v1/visitors

Сырые посетители. Scope visitors. Максимум 7 дней, максимум 500 строк. Вектора embedding нет.

QueryОбязательноКомментарий
start_date / end_dateдамаксимум 7 дней
genderнетmale | female
camera_id / building_idнет
limitнет1–500, по умолчанию 100
Request
1
GET /integration/v1/visitors?start_date=2026-08-17&end_date=2026-08-17&limit=100
2
GET /integration/v1/visitors?start_date=2026-08-17&end_date=2026-08-17&gender=female&building_id=10&camera_id=101&limit=50
JSON
1
[
2
{
3
"id": 45,
4
"face_id": 789,
5
"age": 22,
6
"gender": "female",
7
"building_id": 10,
8
"camera_id": 101,
9
"visit_status": "visitor",
10
"exit_time": null,
11
"face_embeddings": [
12
{
13
"id": 123,
14
"camera_id": 101,
15
"event_time": "2026-08-17T10:30:00",
16
"crop_url": "https://s3.../presigned",
17
"visit_status": "visitor"
18
}
19
]
20
}
21
]

GET/integration/v1/users

Сотрудники. Scope visitors. Нет пароля/hash и face-векторов.

Request
1
GET /integration/v1/users?limit=100
2
GET /integration/v1/users?building_id=10&department_id=2&branch_id=4&gender=male&search_str=ali&limit=50
JSON
1
[
2
{
3
"id": 7,
4
"username": "ali.karimov",
5
"email": "ali@company.com",
6
"first_name": "Ali",
7
"last_name": "Karimov",
8
"gender": "male",
9
"phone": "+998901234567",
10
"position": "Manager",
11
"building_id": 10,
12
"department_id": 2,
13
"branch_id": 4,
14
"is_active": true,
15
"face_photos": [{ "id": 55, "image_url": "https://s3.../presigned" }]
16
}
17
]

Общие ошибки и лимиты

Ответ 401

401
1
{
2
"detail": "Not authenticated"
3
}

Ответ 401

401
1
{
2
"detail": "Could not validate credentials"
3
}

Ответ 401

401
1
{
2
"detail": "Invalid API key"
3
}

Ответ 401

401
1
{
2
"detail": "Staff tokens are not accepted on the integration API"
3
}

Ответ 403

403
1
{
2
"detail": "API key or token is missing scope: analytics"
3
}

Ответ 429

429
1
{
2
"detail": "Too many requests. Please try again later.",
3
"retry_after": 3600
4
}
Группа эндпоинтовЛимит
POST token10 / минуту
/me, /branches, /buildings, /cameras60 / час
branch-summary JSON, hourly, traffic30 / час
export.xlsx5 / час
/visitors10 / минуту

Сценарий клиента

curl 1
1
TOKEN=$(curl -s -X POST 'https://api.retailsolution.ai/integration/v1/token' \
2
-H 'Content-Type: application/x-www-form-urlencoded' \
3
-d 'username=yuksalish_api&password=SECRET' | jq -r .access_token)
curl 2
1
curl -s -H "Authorization: Bearer $TOKEN" \
2
'https://api.retailsolution.ai/integration/v1/me'
curl 3
1
curl -s -H "Authorization: Bearer $TOKEN" \
2
'https://api.retailsolution.ai/integration/v1/branches'
curl 4
1
curl -s -H "Authorization: Bearer $TOKEN" \
2
'https://api.retailsolution.ai/integration/v1/analytics/branch-summary?start_date=2026-08-17&end_date=2026-08-17'
curl 5
1
curl -s -H "Authorization: Bearer cv_live_xxxx" \
2
'https://api.retailsolution.ai/integration/v1/analytics/branch-summary?start_date=2026-08-17&end_date=2026-08-17'