API v2 · в активной разработке

TeleOpen Platform API

Backend-as-a-Service для продавцов VPN-подписок. Заводите серверы, тарифы и подписки через REST API; отдавайте клиентам живые брендированные ссылки teleopen://. Вы пишете только бота и приём оплаты — учёт, ротация серверов, отзыв доступа и формат ссылки берёт на себя платформа.

Что это такое

Обычная ссылка-подписка (vless://, happ://) — это замороженный конфиг. Сменили сервер — нужно перевыдавать ссылку всем клиентам. Отозвать доступ у неплательщика нельзя.

teleopen:// — это указатель на управляемый объект на нашем бэке. Содержимое живёт на сервере, поэтому:

При этом та же ссылка отдаётся и в формате обычной подписки — её проглотит любой сторонний клиент (v2rayN, Happ и пр.), деградируя до простого списка серверов.

Модель данных

Каждый продавец — это изолированный tenant. Внутри своя зона:

Tenant (вы, продавец)
├── API-ключи         — доступ бота к API
├── Nodes             — серверы (vless/ss/trojan/hy2…)
├── Node Pools        — группы нод («Европа», «для стриминга»)
├── Plans             — тарифы (трафик / срок / устройства / пул)
└── Subscriptions     — выданные клиентам подписки
    └── teleopen://    — живая ссылка на каждую подписку
Ключевой принцип: подписка привязана не к серверу, а к тарифу и пулу нод. Вы крутите серверы под капотом — у клиента ничего не отваливается.

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

Все машинные запросы — с заголовком:

Authorization: Bearer tlo_<ваш_api_ключ>

Ключ выпускается в личном кабинете (вход через Telegram) и показывается один раз — мы храним только его хэш. Потеряли — выпустите новый, старый отзовите. У ключа есть скоупы (sub:write, node:write, plan:write, broadcast:send, analytics:read, …); по умолчанию выдаются все.

Быстрый старт за 4 запроса

Базовый URL: https://teleopen.space. Сценарий «клиент оплатил — выдать доступ»:

1

Завести серверы

Можно вывалить весь список разом — строки, base64-подписку или URL подписки:

curl -X POST https://teleopen.space/v2/nodes/import \
  -H "Authorization: Bearer tlo_…" -H "Content-Type: application/json" \
  -d '{"text":"vless://uuid@1.2.3.4:443#NL-1\nvless://uuid@5.6.7.8:443#DE-2"}'

→ {"imported": 2, "node_ids": ["53b2…", "22c4…"]}
2

Собрать пул и тариф

# пул из этих нод
curl -X POST …/v2/pools -H "Authorization: Bearer tlo_…" \
  -d '{"name":"EU","node_ids":["53b2…","22c4…"]}'
→ {"id":"6aad…","name":"EU","strategy":"all", …}

# тариф: 100 ГБ, 30 дней, 3 устройства, пул EU
curl -X POST …/v2/plans -H "Authorization: Bearer tlo_…" \
  -d '{"name":"Premium","traffic_limit":107374182400,
       "duration_days":30,"device_limit":3,"pool_id":"6aad…"}'
→ {"id":"1537…", …}
3

Выдать подписку → получить ссылку

curl -X POST …/v2/subscriptions -H "Authorization: Bearer tlo_…" \
  -d '{"plan_id":"1537…","external_user_id":"tg:55501",
       "renew_url":"https://t.me/yourbot?start=renew"}'

→ {
    "id": "ed53…", "status": "active",
    "expires_at": 1783559423, "traffic_total": 107374182400,
    "link": "teleopen://s/3zY8n5zGquEYMHnA",
    "sub_url": "https://teleopen.space/v2/resolve/3zY8n5zGquEYMHnA?format=sub"
  }

Отдайте link клиенту. Всё — продажа закрыта.

4

Управлять дальше

# продлить на 7 дней (ссылку перевыдавать НЕ нужно)
curl -X PATCH …/v2/subscriptions/ed53… -d '{"action":"extend","days":7}'

# мгновенно отозвать доступ
curl -X DELETE …/v2/subscriptions/ed53…
→ ссылка тут же отдаёт status:"banned", серверов нет

# у кого истекает в ближайшие 3 дня — для рассылки на продление
curl …/v2/subscriptions?expires_in=3d

Короткий живой указатель:

teleopen://s/<code>        напр. teleopen://s/3zY8n5zGquEYMHnA

code — 16 символов base62 (≈95 бит энтропии, не подбирается). Клиент, открыв ссылку, обращается к резолверу за актуальным состоянием.

Резолвер

GET/v2/resolve/{code} публичный · без ключа

Параметр format выбирает представление:

format=json — для нативного клиента TeleOpen

{
  "status": "active",                      // active|trial|expired|frozen|banned|exhausted
  "subscription": {
    "title": "Vasya VPN",
    "expires_at": 1783559423,              // unix, null = бессрочно
    "traffic_total": 107374182400,
    "traffic_used": 0,
    "device_limit": 3,
    "renew_url": "https://t.me/yourbot?start=renew"
  },
  "brand": { "name": "Vasya VPN", "color": "#0A84FF" },
  "nodes": [ {"uri":"vless://…#NL-1"}, {"uri":"vless://…#DE-2"} ],
  "update_interval": 360                   // минут до следующего ре-резолва
}

format=sub — совместимый со сторонними клиентами

Отдаёт стандартную base64-подписку и заголовки profile-title, subscription-userinfo (трафик/срок). Эту форму понимают любые VPN-клиенты-подписчики — одна ссылка работает везде.

Если подписка отозвана / истекла / трафик исчерпан — резолвер возвращает соответствующий status и пустой список серверов, клиент отключается.

Справочник: Tenant и ключи

Эти ручки — из личного кабинета (Telegram-вход), не по API-ключу.

POST/v2/tenantсоздать/получить tenant
GET/v2/tenantинфо + квоты
POST/v2/tenant/keysвыпустить ключ (показ 1 раз)
GET/v2/tenant/keysсписок ключей
DEL/v2/tenant/keys/{id}отозвать

Справочник: Ноды и пулы

POST/v2/nodesдобавить ноду
POST/v2/nodes/importимпорт пачкой (text/url)
GET/v2/nodesсписок
PATCH/v2/nodes/{id}вкл/выкл, теги, гео
DEL/v2/nodes/{id}удалить
POST/v2/poolsсоздать пул
GET/v2/poolsсписок пулов
PATCH/v2/pools/{id}стратегия и состав

Поддерживаемые схемы при импорте: vless vmess trojan ss ssr hysteria hysteria2 hy2 tuic. По умолчанию каждый URI → отдельная нода; {"one_node":true} — все в одну.

Справочник: Тарифы

POST/v2/plansсоздать тариф
GET/v2/plansсписок
PATCH/v2/plans/{id}изменить
DEL/v2/plans/{id}удалить
ПолеТипОписание
namestringназвание (обязательно)
traffic_limitbytesлимит трафика, null = ∞
duration_daysintсрок, null = бессрочно
device_limitintлимит устройств
pool_iduuidкакой пул нод отдавать
kindstringstandard|trial|promo|gift

Справочник: Подписки

POST/v2/subscriptionsвыдать → teleopen://
GET/v2/subscriptionsсписок + фильтры
GET/v2/subscriptions/{id}одна подписка
PATCH/v2/subscriptions/{id}действия (см. ниже)
DEL/v2/subscriptions/{id}мгновенный отзыв
POST/v2/subscriptions/{id}/linksперевыпустить ссылку

Действия PATCH ({"action": "…"})

actionпараметрычто делает
extenddaysпродлить срок
add_trafficbytesдобавить трафик
freeze / unfreezeзаморозить / разморозить
banзабанить
change_planplan_idсменить тариф
reset_devicesсбросить привязки устройств
set_renew_urlrenew_urlссылка на продление

Фильтры GET /v2/subscriptions

параметрпримерописание
statusactiveпо статусу
plan_iduuidпо тарифу
expires_in3d · 24hистекают в окне — для рассылок

Ошибки и лимиты

кодзначение
400неверные параметры запроса
401нет/неверный/отозванный API-ключ
402достигнут лимит подписок вашего тарифа платформы
403не хватает скоупа или tenant приостановлен
404объект не найден (в т.ч. чужой — изоляция tenant'ов)
429превышен rate-limit

Тело ошибки: {"detail": "описание"}. Все объекты строго изолированы по tenant'у — чужие подписки/ноды недоступны (возвращается 404).

Что дальше (roadmap)

Ядро (ноды, пулы, тарифы, подписки, живые ссылки) — работает. В разработке:

Статус: API в активной разработке, контракты v2 могут меняться. За доступом к раннему API и ключами — @TLOPSpace.