TOKEN PAY

· API

Техническая документация закрытого партнёрского API системы аутентификации TOKEN PAY ID. Описывает OAuth 2.0 / PKCE flow, native SDK, webhooks и эндпоинты, доступные утверждённым интеграторам.

Закрытый API: TOKEN PAY ID — это партнёрская платформа идентификации. API-ключи и client_id выдаются только после согласования интеграции. Чтобы получить доступ, напишите на dev@tokenpay.space с описанием проекта, целевой аудитории и ожидаемой нагрузки. Документация ниже доступна публично как справочник по контракту — для реальных запросов понадобятся выданные учётные данные.
Base URL: https://id.tokenpay.space/api/v1

API использует JSON для всех запросов и ответов. Все запросы должны содержать заголовок Content-Type: application/json.

Как получить доступ

1. Написать на dev@tokenpay.space — укажите название проекта, домен, сценарий интеграции и контактное лицо
2. Пройти короткое ревью (обычно 1–3 рабочих дня)
3. После одобрения получить client_id, client_secret и доступ к личному кабинету партнёра
4. Настроить OAuth redirect-URI и использовать API как описано ниже

Быстрый старт (после одобрения)

cURL
curl -X GET https://id.tokenpay.space/api/v1/users/me \
  -H "Authorization: Bearer tpid_sk_your_secret_key" \
  -H "Content-Type: application/json"

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

TOKEN PAY ID API поддерживает два метода аутентификации:

API ключи

Используйте секретный ключ (tpid_sk_...) в заголовке Authorization:

HTTP
Authorization: Bearer tpid_sk_ваш_секретный_ключ
Важно: Никогда не используйте секретный ключ (sk) в клиентском коде. Для фронтенда используйте публичный ключ (pk) и OAuth 2.0 flow.

OAuth 2.0 Bearer Token

Для пользовательских запросов используйте access_token полученный через OAuth 2.0 flow:

HTTP
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Лимиты запросов

API имеет следующие ограничения:

Тарифный планЗапросов/минЗапросов/день
Free6010,000
Pro300100,000
Enterprise1,000Unlimited

Заголовки ответа содержат информацию о лимитах:

Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1678886400

Коды ошибок

API возвращает стандартные HTTP коды и JSON-объект ошибки:

JSON
{
  "error": {
    "code": "invalid_token",
    "message": "The access token is invalid or expired",
    "status": 401
  }
}
КодОписание
400Некорректный запрос
401Неавторизован — неверный или отсутствующий токен
403Запрещено — недостаточно прав
404Ресурс не найден
429Превышен лимит запросов
500Внутренняя ошибка сервера

OAuth 2.0 — Авторизация

TOKEN PAY ID поддерживает стандартный OAuth 2.0 Authorization Code flow для безопасной авторизации пользователей в сторонних сервисах.

Шаг 1: Redirect к авторизации

Перенаправьте пользователя на страницу авторизации:

URL
https://id.tokenpay.space/api/v1/oauth/authorize?
  client_id=tpid_pk_your_public_key&
  redirect_uri=https://yourapp.com/callback&
  response_type=code&
  scope=profile email&
  state=random_csrf_token&
  prompt=login&
  code_challenge=SHA256_HASH&
  code_challenge_method=S256
PKCE обязателен: Начиная с v2.2.0, параметры code_challenge и code_challenge_method=S256 являются обязательными для всех клиентов. Запросы без PKCE будут отклонены.
ПараметрТипОписание
client_idstringrequired — Публичный ключ
redirect_uristringrequired — URL для редиректа
response_typestringrequired — Только code
scopestringЗапрашиваемые разрешения (по умолч. profile)
statestringCSRF-токен (рекомендуется)
promptstringlogin — принудительная переавторизация, consent, none
login_hintstringEmail для предзаполнения формы входа
code_challengestringrequired — PKCE challenge (SHA-256 хеш code_verifier)
code_challenge_methodstringrequired — Только S256

Шаг 2: Обмен кода на токен

После авторизации пользователь будет перенаправлен на ваш redirect_uri с параметром code. Обменяйте его на access_token:

POST /oauth/token

POST /api/v1/oauth/token

Обмен authorization code на access token.

ПараметрТипОписание
grant_typestringrequiredauthorization_code или refresh_token
codestringrequired — Authorization code
client_idstringrequired — Ваш публичный ключ
client_secretstringСекретный ключ (обязателен для confidential-клиентов, не нужен для public + PKCE)
redirect_uristringrequired — Должен совпадать с исходным
code_verifierstringrequired — PKCE verifier (случайная строка 43–128 символов, из которой был создан code_challenge)
cURL
curl -X POST https://id.tokenpay.space/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "abc123...",
    "client_id": "tpid_pk_...",
    "client_secret": "tpid_sk_...",
    "redirect_uri": "https://yourapp.com/callback",
    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  }'

Ответ

JSON — 200 OK
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tpid_rt_8a7b6c5d...",
  "scope": "profile email",
  "user": {
    "id": "tpid_usr_a1b2c3d4",
    "email": "user@example.com",
    "name": "Alex Example",
    "email_verified": true
  }
}

POST /oauth/revoke

POST /api/v1/oauth/revoke

Отзыв access или refresh токена.

ПараметрТипОписание
tokenstringrequired — Токен для отзыва
token_type_hintstringaccess_token или refresh_token

GET /oauth/userinfo

GET /api/v1/oauth/userinfo

Получить профиль пользователя по OAuth access token. Возвращает данные в зависимости от запрошенного scope.

cURL
curl -X GET https://tokenpay.space/api/v1/oauth/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"
JSON — 200 OK
{
  "id": "tpid_usr_a1b2c3d4e5f6",
  "email": "user@example.com",
  "name": "Alex Example",
  "role": "user",
  "email_verified": true,
  "locale": "ru",
  "theme": "dark",
  "telegram_linked": true,
  "cupol_balance": 150,
  "cupol_subscription_active": true,
  "created_at": "2026-01-15T12:00:00Z"
}

POST /oauth/cancel

POST /api/v1/oauth/cancel

Уведомить enterprise о том, что пользователь закрыл окно согласия (вызывается автоматически через sendBeacon). Запускает webhook user.oauth_cancel.

ПараметрТипОписание
client_idstringrequired
reasonstringПричина: window_closed, user_cancel

POST /oauth/deny

POST /api/v1/oauth/deny

Пользователь явно отклонил запрос на авторизацию. Запускает webhook user.oauth_deny и перенаправляет с ошибкой access_denied.

ПараметрТипОписание
client_idstringrequired
redirect_uristringrequired
statestringПередаётся обратно в redirect

GET /oauth/branding

GET /api/v1/oauth/branding

Получить конфигурацию кнопок, иконки и готовый код для интеграции виджета TOKEN PAY ID. Не требует аутентификации.

JSON — 200 OK (сокращённо)
{
  "provider": "TOKEN PAY ID",
  "widget_url": "https://tokenpay.space/sdk/tpid-widget.js",
  "widget_version": "1.2.0",
  "icon": { "shield_svg": "<svg ...>" },
  "buttons": {
    "standard": { "label": "Войти через TOKEN PAY", ... },
    "icon": { "shape": "circle", ... },
    "logo": { "background": "transparent", ... }
  },
  "integration": {
    "quick_start": "<script src=... data-client-id=...>",
    "oauth_popup": "TPID.loginWithOAuth().then(...);"
  },
  "themes": ["dark", "light", "auto"],
  "languages": ["ru", "en"]
}

POST /oauth/register — Dynamic Client Registration (RFC 7591)

POST /api/v1/oauth/register

Зарегистрировать новый OAuth-клиент программно. Требуется авторизация (Bearer token или API ключ).

ПараметрТипОписание
client_namestringrequired — Название приложения
redirect_urisstring[]required — Массив разрешённых redirect URI
client_typestringpublic или confidential (по умолч. public)
client_uristringURL сайта приложения
logo_uristringURL логотипа
tos_uristringURL условий использования
policy_uristringURL политики конфиденциальности
contactsstringКонтактные email (через запятую)
scopestringЗапрашиваемые scope: openid, profile, email
JSON — 201 Created
{
  "client_id": "tpid_pk_...",
  "client_secret": "tpid_sk_...",
  "client_name": "My App",
  "redirect_uris": ["https://myapp.com/callback"],
  "client_type": "public",
  "registration_access_token": "Bearer ..."
}
Важно: client_secret показывается только один раз при регистрации. Сохраните его.

GET /oauth/client-info/:client_id

GET /api/v1/oauth/client-info/:client_id

Получить метаданные зарегистрированного OAuth-клиента. Доступно только владельцу ключа.

JSON — 200 OK
{
  "client_id": "tpid_pk_...",
  "client_name": "My App",
  "client_type": "public",
  "redirect_uris": ["https://myapp.com/callback"],
  "client_uri": "https://myapp.com",
  "logo_uri": null,
  "tos_uri": null,
  "policy_uri": null,
  "scope": "openid,profile,email"
}

Пользователи

GET /users/me

GET /api/v1/users/me

Получить профиль текущего авторизованного пользователя.

JSON — 200 OK
{
  "id": "tpid_usr_a1b2c3d4",
  "email": "user@example.com",
  "name": "Alex Example",
  "email_verified": true,
  "two_factor_enabled": false,
  "telegram_linked": true,
  "created_at": "2025-01-15T10:30:00Z",
  "last_login": "2026-03-13T09:45:00Z"
}

PUT /users/me

PUT /api/v1/users/me

Обновить профиль пользователя.

ПараметрТипОписание
namestringИмя пользователя
avatar_urlstringURL аватара

GET /users/activity

GET /api/v1/users/activity

История активности пользователя: входы, API вызовы, изменения безопасности.

ПараметрТипОписание
limitintegerКоличество записей (по умолчанию 50, максимум 200)
offsetintegerСмещение для пагинации
typestringФильтр: auth, api, security

GET /users/sessions

GET /api/v1/users/sessions

Список активных сессий пользователя.

JSON — 200 OK
{
  "sessions": [
    {
      "id": "ses_abc123",
      "device": "Chrome — Windows",
      "ip": "192.168.1.1",
      "location": "Saint Petersburg, RU",
      "last_active": "2026-03-13T12:00:00Z",
      "current": true
    }
  ]
}

Auth

POST /auth/send-code

POST /api/v1/auth/send-code

Отправить 6-значный код верификации на email. Используется перед регистрацией и при входе.

ПараметрТипОписание
emailstringrequired — Email адрес
typestringrequiredregister или login
Rate limit: 1 код в 60 секунд на email. Код действителен 10 минут.
JSON — 200 OK
{
  "success": true,
  "message": "Verification code sent",
  "expires_in": 600
}

POST /auth/verify

POST /api/v1/auth/verify

Верификация access токена. Используется сторонними сервисами для проверки авторизации пользователя.

ПараметрТипОписание
tokenstringrequired — Access token для проверки
JSON — 200 OK
{
  "valid": true,
  "user_id": "tpid_usr_a1b2c3d4",
  "email": "user@example.com",
  "scopes": ["profile", "email"],
  "expires_at": 1678886400
}

POST /auth/register

POST /api/v1/auth/register

Регистрация нового пользователя. Требуется предварительная отправка кода через /auth/send-code.

ПараметрТипОписание
emailstringrequired
passwordstringrequired — Минимум 8 символов
namestringrequired
email_codestringrequired — 6-значный код из email (от /auth/send-code)
Обязательно: Перед регистрацией необходимо отправить код верификации через POST /auth/send-code с type: "register".

POST /auth/login

POST /api/v1/auth/login

Двухэтапная авторизация. Первый вызов с email+password отправляет код на почту и возвращает requires_email_code: true. Второй вызов с email+password+email_code завершает авторизацию.

ПараметрТипОписание
emailstringrequired
passwordstringrequired
email_codestring6-значный код из email (обязателен на втором шаге)
two_factor_codestringTOTP код, если 2FA включена

Шаг 1 — отправка пароля:

JSON — 200 OK
{
  "requires_email_code": true,
  "requires_2fa": false,
  "message": "Verification code sent to your email"
}

Шаг 2 — подтверждение кода:

Повторите запрос, добавив email_code. При успехе возвращается access/refresh token и данные пользователя.

POST /auth/refresh

POST /api/v1/auth/refresh

Обновление access токена с помощью refresh токена.

ПараметрТипОписание
refresh_tokenstringrequired

QR-вход

Двухэтапный вход через QR-код: десктоп генерирует сессию, мобильное устройство (с активной авторизацией) подтверждает.

1. Инициализация QR-сессии

POST /api/v1/auth/qr/login-init

Создаёт QR-сессию (не требует авторизации). Возвращает sessionId и URL для QR-кода.

JSON — 200 OK
{
  "sessionId": "uuid-...",
  "ttl": 300,
  "qrUrl": "https://tokenpay.space/qr-login?sid=uuid-..."
}

2. Polling (десктоп)

GET /api/v1/auth/qr/login-poll/:sessionId

Десктоп опрашивает каждые 2 секунды. Статус: pendingapproved → возвращает токены.

3. Подтверждение (мобильное устройство)

POST /api/v1/auth/qr/login-confirm/:sessionId

Требует Authorization: Bearer. Мобильное устройство подтверждает вход.

TTL: QR-сессия живёт 5 минут. После истечения возвращается status: "expired".

API ключи

GET /keys

GET /api/v1/keys

Получить список API ключей пользователя.

POST /keys

POST /api/v1/keys

Создать новый API ключ.

ПараметрТипОписание
namestringrequired — Название ключа
scopesstring[]Массив разрешений
Важно: Секретный ключ показывается только один раз при создании. Сохраните его в безопасном месте.

DELETE /keys/:id

DEL /api/v1/keys/:key_id

Отозвать (удалить) API ключ. Все запросы с этим ключом немедленно перестанут работать.

Webhooks

Webhooks позволяют получать уведомления о событиях TOKEN PAY ID в реальном времени. Настройте URL вашего сервера в личном кабинете.

Каждый webhook запрос содержит подпись в заголовке X-TPID-Signature формата Stripe-style для верификации подлинности:

Header
X-TPID-Signature: t=1711000000,v1=hmac_sha256_hex

Для проверки подписи:

JavaScript
const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const parts = {};
  signature.split(',').forEach(p => {
    const [k, v] = p.split('=');
    parts[k] = v;
  });
  const expected = crypto
    .createHmac('sha256', secret)
    .update(parts.t + '.' + JSON.stringify(body))
    .digest('hex');
  return parts.v1 === expected;
}
Replay protection: Проверяйте t (timestamp) — отклоняйте запросы старше 5 минут.

Payload

JSON — Webhook Payload
{
  "event": "user.login",
  "timestamp": "2026-03-13T12:00:00Z",
  "data": {
    "user_id": "tpid_usr_a1b2c3d4",
    "ip": "192.168.1.1",
    "device": "Chrome — Windows"
  }
}

Доступные события

СобытиеОписание
user.registerНовая регистрация пользователя
user.loginУспешный вход в аккаунт
user.logoutВыход из аккаунта
user.updatedПрофиль обновлён
user.deletedАккаунт удалён
user.2fa.enabled2FA включена
user.2fa.disabled2FA отключена
key.createdСоздан новый API ключ
key.revokedAPI ключ отозван
session.createdНовая сессия
session.revokedСессия завершена
token.refreshedТокен обновлён
user.oauth_connectПользователь одобрил OAuth авторизацию
user.oauth_cancelПользователь закрыл окно OAuth
user.oauth_denyПользователь отклонил OAuth запрос

Push Notifications API

Система push-уведомлений позволяет получать уведомления в реальном времени через SSE (Server-Sent Events) или через REST API.

GET /api/v1/notifications

Получить историю уведомлений (последние 50). Требует Authorization: Bearer <token>.

Response
{
  "notifications": [
    { "id": "...", "type": "oauth_connect", "title": "New user connected", "body": "user@example.com authorized via OAuth", "is_read": false, "created_at": "2026-04-02T12:00:00Z" }
  ],
  "unread": 3
}
PUT /api/v1/notifications/:id/read

Отметить уведомление как прочитанное.

PUT /api/v1/notifications/read-all

Отметить все уведомления как прочитанные.

SSE Stream (Server-Sent Events)

GET /api/v1/notifications/stream?token=<jwt>

Подключение к потоку уведомлений в реальном времени. JWT передаётся через query-параметр (EventSource не поддерживает заголовки).

JavaScript
const es = new EventSource('/api/v1/notifications/stream?token=' + token);
es.addEventListener('notification', e => {
  const data = JSON.parse(e.data);
  console.log(data.event, data.data.title);
});
es.addEventListener('connected', e => {
  console.log('SSE connected', JSON.parse(e.data));
});
События: oauth_connect, oauth_cancelled, oauth_denied, key_created, key_revoked, user_unlink

Widget SDK v1.2 — Быстрый старт

Виджет TOKEN PAY ID предоставляет три варианта кнопок для интеграции: стандартная, круглая иконка и прозрачный логотип. Все кнопки автоматически работают с OAuth 2.0 + PKCE.

CDN: https://tokenpay.space/sdk/tpid-widget.js
Версия 1.2.0 — автообновления, без кэширования.

Минимальная интеграция (1 строка)

HTML
<script src="https://tokenpay.space/sdk/tpid-widget.js"
        data-client-id="tpid_pk_ваш_ключ"></script>

Виджет автоматически создаст кнопку «Войти через TOKEN PAY» на странице.

Три варианта кнопок

ВариантАтрибутОписание
standarddata-tpid-button="standard"Полная кнопка с иконкой щита + текст «Войти через TOKEN PAY». Подходит для основных CTA.
icondata-tpid-button="icon"Круглая кнопка с иконкой щита. Идеально для навбара рядом с Google/Apple.
logodata-tpid-button="logo"Кнопка без фона — только логотип + текст. Для минималистичного дизайна.
HTML — Все 3 варианта
<!-- Стандартная кнопка -->
<div data-tpid-button="standard"></div>

<!-- Круглая иконка -->
<div data-tpid-button="icon"></div>

<!-- Логотип без фона -->
<div data-tpid-button="logo"></div>

OAuth Popup Flow (Promise API)

Метод TPID.loginWithOAuth() открывает popup, проводит OAuth flow и возвращает Promise с authorization code.

JavaScript
TPID.loginWithOAuth({ prompt: 'login' })
  .then(function(result) {
    console.log('Auth code:', result.code);
    console.log('State:', result.state);
    // Отправьте code на ваш сервер для обмена на токен
  })
  .catch(function(err) {
    console.error('OAuth error:', err);
  });
prompt=login гарантирует, что пользователь увидит форму входа, даже если уже авторизован. Это решает проблему «залогиненного админа».

Программный API (TPID)

МетодОписание
TPID.init(opts)Инициализация с clientId, redirectUri, onSuccess, onError, lang, autoButton
TPID.open()Открыть окно авторизации (redirect flow)
TPID.loginWithOAuth(opts)OAuth popup flow → Promise<{code, state}>
TPID.renderButton(el, opts)Рендер стандартной кнопки в контейнер
TPID.renderIconButton(el, opts)Рендер круглой иконки
TPID.renderLogoButton(el, opts)Рендер прозрачной кнопки-логотипа
TPID.versionТекущая версия виджета (1.2.0)

SDK — JavaScript v2.0 (tokenpay-auth.js)

OAuth SDK с автоматической генерацией PKCE (S256). Рекомендуется как основной SDK для серверных и SPA-приложений. Для простой интеграции кнопок см. Widget SDK v1.2.

CDN: https://tokenpay.space/sdk/tokenpay-auth.js
v2.0.0 — PKCE (S256) генерируется автоматически. code_verifier сохраняется в sessionStorage.

Два типа кнопок

ТипВидОписание
icondata-mode="icon"Круглая иконка (щит TOKEN PAY) — как у Google/Apple. Размеры: 36×36 / 44×44 / 54×54 px
logodata-mode="logo"Прямоугольная кнопка с иконкой + текст «Войти через TOKEN PAY». Min/max ширина ограничена.

1. Быстрое подключение (data-атрибуты)

HTML — Кнопка LOGO (прямоугольная)
<!-- Прямоугольная кнопка с текстом -->
<script src="https://tokenpay.space/sdk/tpid-auth.js"
        data-client-id="tpid_pk_ваш_ключ"
        data-redirect-uri="https://ваш-сайт.com/callback"
        data-mode="logo"
        data-theme="dark"
        data-size="medium"
        data-lang="ru">
</script>
HTML — Кнопка ICON (круглая)
<!-- Круглая иконка -->
<script src="https://tokenpay.space/sdk/tpid-auth.js"
        data-client-id="tpid_pk_ваш_ключ"
        data-redirect-uri="https://ваш-сайт.com/callback"
        data-mode="icon"
        data-theme="light"
        data-size="large">
</script>

2. Программный API

JavaScript
// Инициализация
TokenPayAuth.init({
  clientId: 'tpid_pk_ваш_ключ',
  redirectUri: 'https://ваш-сайт.com/callback',
  theme: 'dark',
  locale: 'ru',
  onSuccess: function(data) {
    console.log('Код:', data.code);
    console.log('Verifier:', data.codeVerifier);
    // Отправьте code + codeVerifier на ваш сервер
  }
});

// Рендер кнопки в контейнер
TokenPayAuth.renderButton('#my-container', {
  mode: 'button', // 'button' | 'icon'
  theme: 'dark',   // 'dark' | 'light' | 'auto'
  size: 'medium',  // 'small' | 'medium' | 'large'
  locale: 'ru'
});

// Или запуск OAuth вручную (PKCE генерируется автоматически)
TokenPayAuth.authorize();

// Получить code_verifier для серверного обмена
const verifier = TokenPayAuth.getCodeVerifier();

3. Обмен кода на токен (серверная сторона)

cURL
curl -X POST https://tokenpay.space/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "tpid_code_...",
    "client_id": "tpid_pk_...",
    "client_secret": "tpid_sk_...",
    "redirect_uri": "https://ваш-сайт.com/callback",
    "code_verifier": "PKCE_VERIFIER"
  }'

4. Получение профиля пользователя

cURL
curl -X GET https://tokenpay.space/api/v1/oauth/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"
JSON — 200 OK
{
  "id": "tpid_usr_a1b2c3d4e5f6",
  "email": "user@example.com",
  "name": "Alex Example",
  "email_verified": true,
  "created_at": "2026-01-15T12:00:00Z"
}

Параметры

Параметр / АтрибутТипОписание
clientId / data-client-idstringrequired — Публичный ключ API (tpid_pk_...)
redirectUri / data-redirect-uristringrequired — URL для редиректа после авторизации
mode / data-modestringТип кнопки: icon (круглая) или logo (прямоугольная, по умолчанию)
theme / data-themestringТема: dark (белая кнопка), light (тёмная кнопка), auto
size / data-sizestringРазмер: small (36px), medium (44px), large (54px)
lang / data-langstringЯзык: ru или en
scope / data-scopestringOAuth scope (по умолчанию openid email profile)
onSuccessfunctionCallback: ({code, state, type}) => {}
onErrorfunctionCallback при ошибке

Ограничения размеров

РежимSmallMediumLarge
icon36×36 px44×44 px54×54 px
logoh:36, w:180–320 pxh:44, w:220–400 pxh:54, w:260–480 px

SDK — Python

Install
pip install tokenpay-id
Python
from tokenpay_id import TokenPayID

client = TokenPayID(
    client_id="tpid_pk_your_public_key",
    client_secret="tpid_sk_your_secret_key"
)

# Verify a user's token
result = client.verify(access_token)
print(result.user_id)  # tpid_usr_...

# Get user profile
user = client.get_user(access_token)
print(user.email)  # user@example.com

# Webhook signature verification
is_valid = client.verify_webhook(
    payload=request.body,
    signature=request.headers["X-TPID-Signature"]
)

SDK — Go

Install
go get github.com/tokenpay/id-go
Go
package main

import (
    tpid "github.com/tokenpay/id-go"
)

func main() {
    client := tpid.NewClient(tpid.Config{
        ClientID:     "tpid_pk_your_public_key",
        ClientSecret: "tpid_sk_your_secret_key",
    })

    // Verify token
    result, err := client.Verify(accessToken)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.UserID) // tpid_usr_...
}

Live preview — кнопки и виджет нативных SDK

v2.6.0 Точная HTML/CSS-реплика того, как нативный TpidLoginButton и виджет TpidAuth.signIn() выглядят в Android (Jetpack Compose), Swift (SwiftUI) и JVM Desktop (Compose for Desktop). UI-код в трёх SDK ~95 % общий и даёт пиксель-в-пиксель ту же картинку, что вы видите ниже.

Три варианта кнопки входа

Согласно brand guidelines — на нативной кнопке только официальный логотип, без текстовых меток. Это снимает вопрос локализации и даёт мгновенную узнаваемость.

DARK PILL · variant = DARK для светлого фона приложения
LIGHT PILL · variant = LIGHT для тёмного фона приложения
ICON ONLY · variant = ICON_ONLY 48×48 — tab‑bar, header‑action, FAB
Использование — Kotlin / Compose
// 1. Dark pill (default) — for light app surfaces
TpidLoginButton(onResult = ::handleLogin)

// 2. Light pill — for dark app surfaces
TpidLoginButton(
    variant = TpidButtonVariant.LIGHT,
    onResult = ::handleLogin
)

// 3. Icon-only — for tab bars, FABs, compact areas
TpidLoginButton(
    variant = TpidButtonVariant.ICON_ONLY,
    onResult = ::handleLogin
)

Виджет авторизации — точная копия Compose / SwiftUI экрана

Виджет открывается полноэкранно (или в Sheet/Dialog на десктопе) и проводит пользователя через welcome → email → пароль → код / 2FA → success. Переключите тему и язык — увидите ровно то же поведение, что у нативных SDK через TpidConfig.theme и TpidConfig.language.

maria@example.com

maria@example.com

maria@example.com

Готово к интеграции. Этот же UI вы получаете одной строкой кода: TpidLoginButton(onResult = …) в Compose, TpidLoginButton(onResult: …) в SwiftUI или tpidLoginButton(onResult = …) в Compose for Desktop. Полные примеры — ниже по разделам.

SDK — Kotlin / Android

v2.6.0 — Native widget (Jetpack Compose). Полностью нативный UI без браузера и WebView. Download: tokenpay-id-android-2.6.0.tar.gz · install instructions · API контракт.
Публикация в Maven Central — Q2 2026. Сейчас — source release по NDA: dev@tokenpay.space.
settings.gradle.kts · v2.6.0 (native)
// 1. curl -L -O https://tokenpay.space/sdk/android/tokenpay-id-android-2.6.0.tar.gz
// 2. tar -xzf tokenpay-id-android-2.6.0.tar.gz
// 3. In settings.gradle.kts:
include(":tokenpay-id-sdk")
project(":tokenpay-id-sdk").projectDir = file("sdk/android/tokenpay-id-sdk")

// In app/build.gradle.kts:
dependencies {
    implementation(project(":tokenpay-id-sdk"))
}

Native widget (v2.4) — одна строка на Compose

Kotlin · Jetpack Compose
import space.tokenpay.id.TpidAuth
import space.tokenpay.id.TpidConfig
import space.tokenpay.id.TpidLoginButton
import space.tokenpay.id.TpidResult

// In Application.onCreate():
TpidAuth.initialize(
    context = applicationContext,
    config = TpidConfig(
        clientId = "tpid_pk_your_public_key",
        redirectUri = "com.cupol.vpn:/auth/callback",
        scopes = listOf("openid", "profile", "email"),
        theme = TpidConfig.Theme.AUTO,
    )
)

// In Compose — native widget opens in an Activity, no browser:
@Composable
fun LoginScreen() {
    TpidLoginButton { result ->
        when (result) {
            is TpidResult.Success -> navigate(result.accessToken)
            is TpidResult.Cancelled -> {}
            is TpidResult.Failure -> showError(result.error)
        }
    }
}

Legacy (v2.2) — OAuth + system browser

Старое API, оставлено для обратной совместимости. Новые интеграции — только v2.4 native widget выше.

Kotlin · legacy browser-based v2.2
// Legacy — requires browser redirect via Custom Tab
Kotlin
import space.tokenpay.id.TokenPayID

val client = TokenPayID(
    clientId = "tpid_pk_your_public_key",
    clientSecret = "tpid_sk_your_secret_key",
    redirectUri = "myapp://callback"
)

// OAuth — открыть браузер
val authUrl = client.getAuthorizationUrl(
    scopes = listOf("profile", "email"),
    usePKCE = true
)
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)))

// Обработка callback в Activity
override fun onNewIntent(intent: Intent) {
    val code = intent.data?.getQueryParameter("code") ?: return
    lifecycleScope.launch {
        val tokens = client.exchangeCode(code)
        val user = client.getUserInfo(tokens.accessToken)
        Log.d("TPID", "User: ${user.name} (${user.email})")
    }
}

// Верификация токена на сервере
val result = client.verify(accessToken)
println(result.userId) // tpid_usr_...

SDK — Swift (iOS / iPadOS / macOS / tvOS / watchOS / visionOS)

Единый SDK для всей Apple-экосистемы. Поддерживает iOS 15+, iPadOS 15+, macOS 12+, tvOS 15+, watchOS 8+, visionOS 1+.

v2.6.0 — Native widget (SwiftUI). Без ASWebAuthenticationSession, без браузера. Download: tokenpay-id-swift-2.6.0.tar.gz · install · API контракт.
SPM registry publication — Q2 2026. Pre-release: source по NDA через dev@tokenpay.space.
Local SPM · v2.6.0 (native)
// 1. curl -L -O https://tokenpay.space/sdk/swift/tokenpay-id-swift-2.6.0.tar.gz
// 2. shasum -a 256 -c tokenpay-id-swift-2.6.0.tar.gz.sha256
// 3. tar -xzf tokenpay-id-swift-2.6.0.tar.gz
// 4. Xcode → File → Add Package Dependencies… → Add Local…
//    Select the unpacked directory. Add "TokenPayID" product to your target.

// Or, in your own Package.swift:
dependencies: [
    .package(path: "./tokenpay-id-swift-2.6.0/sdk/swift")
]

Native widget (v2.4) — SwiftUI

Swift · SwiftUI (iOS 15+, macOS 12+, tvOS/watchOS/visionOS)
import SwiftUI
import TokenPayID

@main
struct MyApp: App {
    init() {
        TpidAuth.shared.initialize(config: .init(
            clientId: "tpid_pk_your_public_key",
            redirectURI: URL(string: "com.cupol.vpn:/auth/callback")!,
            scopes: ["openid", "profile", "email"],
            theme: .auto
        ))
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

struct ContentView: View {
    var body: some View {
        // Native widget — opens as a SwiftUI sheet, NOT ASWebAuthenticationSession
        TpidLoginButton { result in
            switch result {
            case .success(let session):
                print("Signed in: \(session.user.email)")
            case .cancelled:
                break
            case .failure(let err):
                print("Error: \(err.code) — \(err.localizedDescription)")
            }
        }
    }
}

Device Flow (tvOS / watchOS)

Swift · Device Flow RFC 8628
let device = try await TpidAuth.shared.startDeviceFlow()
// device.userCode            → "ABCD-EFGH"
// device.verificationURIComplete → display as QR on screen
let session = try await TpidAuth.shared.pollDeviceFlow(device)

Legacy (v2.3) — ASWebAuthenticationSession

Старый browser-based flow. Оставлен для обратной совместимости. Новые интеграции — только v2.4 native widget выше.

Swift (iOS 13+)
import SwiftUI
import AuthenticationServices
import TokenPayID

class AuthManager: NSObject, ObservableObject, ASWebAuthenticationPresentationContextProviding {
    let client = TPIDClient(
        clientId: "tpid_pk_your_public_key",
        redirectURI: "myapp://callback"  // public client, no secret on iOS
    )

    @Published var user: TPIDUser?
    @Published var isLoading = false
    private var session: ASWebAuthenticationSession?

    func signIn() {
        let (authURL, verifier) = client.authorizationURL(
            scopes: [.profile, .email],
            usePKCE: true
        )
        isLoading = true
        session = ASWebAuthenticationSession(
            url: authURL,
            callbackURLScheme: "myapp"
        ) { [weak self] callbackURL, error in
            guard let self, let url = callbackURL,
                  let code = url.queryParam("code") else {
                DispatchQueue.main.async { self?.isLoading = false }
                return
            }
            Task { @MainActor in
                do {
                    let tokens = try await self.client.exchangeCode(code, verifier: verifier)
                    try TPIDKeychain.save(tokens)  // secure storage
                    self.user = try await self.client.userInfo(tokens.accessToken)
                } catch { print("Auth error: \(error)") }
                self.isLoading = false
            }
        }
        session?.presentationContextProvider = self
        session?.prefersEphemeralWebBrowserSession = false  // share cookies with Safari
        session?.start()
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        UIApplication.shared.connectedScenes
            .compactMap { ($0 as? UIWindowScene)?.windows.first }
            .first { $0.isKeyWindow } ?? ASPresentationAnchor()
    }
}

// SwiftUI View
struct ContentView: View {
    @StateObject var auth = AuthManager()
    var body: some View {
        if let user = auth.user {
            Text("Привет, \(user.name)!")
        } else {
            Button("Войти через TOKEN PAY ID") { auth.signIn() }
                .disabled(auth.isLoading)
        }
    }
}

macOS — SwiftUI AppKit

Swift (macOS 10.15+)
import AppKit
import AuthenticationServices
import TokenPayID

// Добавьте в Info.plist:
// CFBundleURLTypes → CFBundleURLSchemes → "myapp"

class MacAuth: NSObject, ASWebAuthenticationPresentationContextProviding {
    let client = TPIDClient(
        clientId: "tpid_pk_your_public_key",
        redirectURI: "myapp://callback"
    )

    func authenticate() async throws -> TPIDUser {
        let (authURL, verifier) = client.authorizationURL(
            scopes: [.profile, .email], usePKCE: true
        )
        let callback: URL = try await withCheckedThrowingContinuation { cont in
            let s = ASWebAuthenticationSession(
                url: authURL, callbackURLScheme: "myapp"
            ) { url, err in
                if let url { cont.resume(returning: url) }
                else { cont.resume(throwing: err ?? TPIDError.cancelled) }
            }
            s.presentationContextProvider = self
            s.start()
        }
        guard let code = callback.queryParam("code") else { throw TPIDError.noCode }
        let tokens = try await client.exchangeCode(code, verifier: verifier)
        try TPIDKeychain.save(tokens)
        return try await client.userInfo(tokens.accessToken)
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        NSApp.keyWindow ?? NSApp.windows.first ?? ASPresentationAnchor()
    }
}

Linux / server-side Swift — raw URLSession (Foundation)

Для headless использования (Vapor, Kitura, CLI) — SDK работает без UI через пару HTTP-вызовов:

Swift (Linux/Vapor)
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking  // Linux
#endif
import TokenPayID

let client = TPIDClient(
    clientId: ProcessInfo.processInfo.environment["TPID_CLIENT_ID"]!,
    clientSecret: ProcessInfo.processInfo.environment["TPID_CLIENT_SECRET"]!,
    redirectURI: "https://api.myapp.com/auth/callback"
)

// 1) Генерация URL авторизации — редиректим пользователя сюда
let (authURL, verifier) = client.authorizationURL(
    scopes: [.profile, .email, .openid],
    state: UUID().uuidString,
    usePKCE: true
)
// Сохраните verifier в сессии (Redis/memcached)

// 2) Callback handler — обмен кода на токены
func handleCallback(code: String, verifier: String) async throws -> TPIDUser {
    let tokens = try await client.exchangeCode(code, verifier: verifier)
    return try await client.userInfo(tokens.accessToken)
}

// 3) Vapor integration
app.get("auth", "login") { req in
    let (url, v) = client.authorizationURL(scopes: [.profile, .email], usePKCE: true)
    req.session.data["verifier"] = v
    return req.redirect(to: url.absoluteString)
}
app.get("auth", "callback") { req async throws -> Response in
    let code = try req.query.get(String.self, at: "code")
    let v = req.session.data["verifier"] ?? ""
    let user = try await handleCallback(code: code, verifier: v)
    req.session.data["user_id"] = user.id
    return req.redirect(to: "/dashboard")
}

Keychain-хранилище токенов (iOS / macOS)

Swift
// SDK встроенно использует Keychain через TPIDKeychain
try TPIDKeychain.save(tokens)                   // сохранить
let tokens = try TPIDKeychain.load()            // загрузить
try TPIDKeychain.clear()                        // выход

// Автоматический refresh при истечении access_token
let freshTokens = try await client.ensureFresh(tokens)

// Отзыв при logout
try await client.revoke(tokens.refreshToken)
try TPIDKeychain.clear()
Поддержка платформ: iOS 13+, iPadOS 13+, macOS 10.15+, tvOS 13+, watchOS 6+, Linux (Ubuntu 20.04+, Amazon Linux 2, Swift 5.5+). Один и тот же API работает везде — разница только в UI-обёртке (ASWebAuthenticationSession на Apple-устройствах, прямые HTTP-вызовы на сервере/Linux).

SDK — JVM Desktop (Kotlin + Compose for Desktop)

v2.6.0 — Единый native widget для Windows / Linux / macOS desktop JVM. Download: tokenpay-id-jvm-desktop-2.6.0.tar.gz · install.
Построен на JetBrains Compose for Desktop 1.6. 90%+ кода UI совпадает с Android SDK (та же программная модель Jetpack Compose).

Единый Kotlin-пакет для десктопных JVM-приложений на Windows 10/11, Linux (любой X11/Wayland с GNOME/KDE), macOS 12+. Работает в любом JVM-приложении: standalone Compose Desktop, встраивание в Swing через ComposePanel, в JavaFX через SwingNode. Min JDK 17.

1. Подключение
// 1. Download + verify
curl -L -O https://tokenpay.space/sdk/jvm-desktop/tokenpay-id-jvm-desktop-2.6.0.tar.gz
sha256sum -c tokenpay-id-jvm-desktop-2.6.0.tar.gz.sha256
tar -xzf tokenpay-id-jvm-desktop-2.6.0.tar.gz

// 2. settings.gradle.kts
include(":tokenpay-id-jvm")
project(":tokenpay-id-jvm").projectDir = file("sdk/jvm-desktop/tokenpay-id-jvm")

// 3. app/build.gradle.kts
plugins {
    id("org.jetbrains.compose") version "1.6.10"
}
dependencies {
    implementation(project(":tokenpay-id-jvm"))
}
2. Standalone Compose Desktop app
import androidx.compose.ui.window.*
import space.tokenpay.id.jvm.*

fun main() = application {
    TpidAuth.initialize(TpidConfig(
        clientId = "tpid_pk_your_key",
        redirectUri = "cupol-vpn://auth/callback"
    ))
    Window(onCloseRequest = ::exitApplication, title = "My App") {
        TpidLoginButton { result ->
            when (result) {
                is TpidResult.Success -> println("signed in as ${result.user.email}")
                is TpidResult.Failure -> println("error: ${result.error.code}")
                TpidResult.Cancelled -> {}
            }
        }
    }
}
3. Встраивание в Swing / JavaFX
// Swing
import androidx.compose.ui.awt.ComposePanel
val panel = ComposePanel()
panel.setContent { TpidLoginButton { /* ... */ } }
jframe.add(panel)

// JavaFX — wrap ComposePanel in SwingNode
val node = SwingNode()
SwingUtilities.invokeLater {
    val p = ComposePanel()
    p.setContent { TpidLoginButton { /* ... */ } }
    node.setContent(p)
}

Хранение refresh-токена:

  • Windows → DPAPI через JNA (Crypt32.dll: CryptProtectData/CryptUnprotectData). Привязано к Windows-аккаунту.
  • macOS → Keychain через CLI security add-generic-password. iCloud Keychain по умолчанию выключен — токен не синхронизируется между устройствами.
  • Linux → libsecret через secret-tool (GNOME Keyring / KWallet). Работает со всеми популярными DE.
  • Fallback — AES-GCM 256 файл в ~/.local/share / %APPDATA% / ~/Library/Application Support. Ключ выведен через PBKDF2 от user.home + user.name + machine-id; скопированный файл на другой машине не расшифровать.

Полная документация на отдельной странице: tokenpay.space/sdk#jvm.

SDK — C# / .NET

NuGet
dotnet add package TokenPayID --version 2.2.0
C#
using TokenPayID;

var client = new TPIDClient(
    clientId: "tpid_pk_your_public_key",
    clientSecret: "tpid_sk_your_secret_key",
    redirectUri: "https://yourapp.com/callback"
);

// Генерация OAuth URL (с PKCE)
var (authUrl, verifier) = client.GetAuthorizationUrl(
    scopes: new[] { "profile", "email" },
    usePKCE: true
);

// Обмен кода на токены
var tokens = await client.ExchangeCodeAsync(code, verifier);
var user = await client.GetUserInfoAsync(tokens.AccessToken);
Console.WriteLine($"User: {user.Name} ({user.Email})");

// ASP.NET — middleware
services.AddAuthentication()
    .AddTokenPayID(options => {
        options.ClientId = "tpid_pk_...";
        options.ClientSecret = "tpid_sk_...";
    });

SDK — PHP

Composer
composer require tokenpay/id-php
PHP
use TokenPay\ID\Client;

$client = new Client([
    'client_id'     => 'tpid_pk_your_public_key',
    'client_secret' => 'tpid_sk_your_secret_key',
    'redirect_uri'  => 'https://yourapp.com/callback',
]);

// Редирект на авторизацию (с PKCE)
[$authUrl, $verifier] = $client->getAuthorizationUrl(['profile', 'email']);
$_SESSION['tpid_verifier'] = $verifier;
header("Location: $authUrl");

// Callback — обмен кода
$tokens = $client->exchangeCode($_GET['code'], $_SESSION['tpid_verifier']);
$user = $client->getUserInfo($tokens->access_token);
echo "Hello, {$user->name}!";

// Laravel — встроенный Socialite драйвер
// config/services.php
'tokenpay' => [
    'client_id' => env('TPID_CLIENT_ID'),
    'client_secret' => env('TPID_CLIENT_SECRET'),
    'redirect' => '/auth/tokenpay/callback',
]

SDK — Ruby

Gemfile
gem 'tokenpay-id', '~> 2.2'
Ruby
require 'tokenpay/id'

client = TokenPay::ID::Client.new(
  client_id: 'tpid_pk_your_public_key',
  client_secret: 'tpid_sk_your_secret_key',
  redirect_uri: 'https://yourapp.com/callback'
)

# OAuth URL
auth_url, verifier = client.authorization_url(
  scopes: ['profile', 'email'],
  pkce: true
)

# Обмен кода
tokens = client.exchange_code(params[:code], verifier)
user = client.user_info(tokens.access_token)
puts "#{user.name} (#{user.email})"

# Rails — OmniAuth стратегия
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :tokenpay_id,
    ENV['TPID_CLIENT_ID'],
    ENV['TPID_CLIENT_SECRET']
end

SDK — Dart / Flutter

pubspec.yaml
dependencies:
  tokenpay_id: ^2.2.0
Dart
import 'package:tokenpay_id/tokenpay_id.dart';

final client = TPIDClient(
  clientId: 'tpid_pk_your_public_key',
  clientSecret: 'tpid_sk_your_secret_key',
  redirectUri: 'myapp://callback',
);

// Flutter — OAuth через url_launcher
final authUrl = client.getAuthorizationUrl(
  scopes: ['profile', 'email'],
  usePKCE: true,
);
await launchUrl(Uri.parse(authUrl));

// Обработка deep link
void handleCallback(Uri uri) async {
  final code = uri.queryParameters['code']!;
  final tokens = await client.exchangeCode(code);
  final user = await client.getUserInfo(tokens.accessToken);
  print('Hello, ${user.name}!');
}

SDK — Rust

Cargo.toml
[dependencies]
tokenpay-id = "2.2"
Rust
use tokenpay_id::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(
        "tpid_pk_your_public_key",
        "tpid_sk_your_secret_key",
    );

    // Верификация токена
    let result = client.verify(access_token).await?;
    println!("User: {}", result.user_id);

    // OAuth URL с PKCE
    let (auth_url, verifier) = client.authorization_url(
        &["profile", "email"],
        "https://yourapp.com/callback",
    )?;

    // Обмен кода
    let tokens = client.exchange_code(code, &verifier).await?;
    let user = client.user_info(&tokens.access_token).await?;
    println!("{} ({})", user.name, user.email);

    Ok(())
}

Desktop & Native Integration

Для десктопных приложений (Electron, Tauri, WPF, SwiftUI, Kotlin Desktop) используется OAuth через системный браузер с перехватом deep link.

Поддерживаемые платформы

ПлатформаМетодRedirect URI
iOS / iPadOSASWebAuthenticationSession + Universal Linkmyapp://callback или https://app.com/cb
macOSASWebAuthenticationSession + Custom URL schememyapp://callback
AndroidCustom Tabs + Intent filtermyapp://callback
Linux (Ubuntu/Debian/Fedora/Arch)xdg-open + localhost HTTP serverhttp://127.0.0.1:PORT/callback
Windows (WPF/WinUI/UWP)URI activation / Custom protocolmyapp://callback
Electron (Win/macOS/Linux)Custom protocol + second-instancemyapp://callback
Tauri (Win/macOS/Linux)Deep link pluginmyapp://callback
Flutter (всё)uni_links / app_linksmyapp://callback
CLI / Server / HeadlessLocalhost HTTP callbackhttp://127.0.0.1:PORT/callback
Embedded (ESP32, RPi Pico W)WiFiManager + mDNShttp://device.local/callback

Параметры для десктопных приложений

URL параметры
GET https://tokenpay.space/api/v1/oauth/authorize
  ?client_id=tpid_pk_...
  &redirect_uri=myapp://callback     // custom scheme
  &response_type=code
  &scope=profile email
  &state=RANDOM_STATE
  &code_challenge=BASE64URL(SHA256(verifier))
  &code_challenge_method=S256
  &theme=dark                         // dark | light — match app theme
  &lang=ru                            // ru | en | zh — UI language

Native Widget v2 — концепция

В отличие от tpid-widget.js (работает в браузере) и OAuth-редиректа через системный браузер, Native Widget v2 — это полностью нативный экран логина, встроенный в ваше приложение. Никаких WebView, никаких переключений в браузер — пользователь не выходит из вашего UX. Идеология совпадает с Apple Sign In и Google One Tap.

Что получает интегратор:
— Пиксельно-одинаковый брендинг TPID на всех платформах (iOS/Android/Windows/macOS/Linux/TV/Wear)
— Полный auth-flow без браузера: email, пароль, email-код, 2FA, passkey/biometric, регистрация, восстановление
— Приложение получает только OAuth code (а не пароль) — всё через стандартный PKCE flow
— Анти-фишинговый бейдж «Официальный TOKEN PAY ID» на каждом экране
— Единый API-контракт во всех SDK (Kotlin/Swift/C#/Dart/Rust/KMP)

Как это работает под капотом

1. SDK при инициализации запрашивает GET /api/v1/sdk/config?client_id=... — получает брендинг, доступные методы авторизации, TLS-пины
2. При клике TpidLoginButton SDK открывает модальный native-экран с UI TPID
3. Перед показом экрана пароля SDK вызывает POST /auth/account-check — узнаёт доступные для юзера методы (password / passkey / TOTP) и показывает правильный экран
4. Все auth-запросы идут напрямую на TPID API через HTTPS с certificate pinning
5. После успешного входа — стандартный OAuth PKCE code → callback в приложение → обмен на access_token

ПлатформаTierSDK
Android 7.0+ (Jetpack Compose)Tier 1Maven Central AAR
iOS 14+ / iPadOS 14+ (SwiftUI)Tier 1SwiftPM + CocoaPods
macOS 12+ (SwiftUI/AppKit)Tier 1SwiftPM
Windows 10 1809+ (WinUI 3/WPF)Tier 1NuGet
Linux glibc 2.31+ (Qt 6/GTK 4)Tier 1deb, rpm, Flatpak, AppImage, pkgconfig
Flutter 3.19+ (cross)Tier 1pub.dev
React Native 0.72+Tier 1npm + native bridges
Kotlin/Compose Multiplatform 1.9+Tier 1Maven Central
watchOS / Wear OS / tvOS / Android TV / Fire TVTier 2Device Flow (RFC 8628)
Xbox / PlayStation / SwitchTier 2Device Flow + companion app
Tizen / webOS / HarmonyOSTier 2JS widget или Device Flow
Tauri / ElectronTier 2npm с N-API биндингами

API контракт (единый на всех SDK)

Независимо от языка конфигурация/результат/ошибки имеют одинаковую форму. Это упрощает кросс-платформенную интеграцию и перевод документации.

TpidWidget.Config
TpidWidget.Config {
  clientId: String             // required — ваш tpid_pk_...
  redirectUri: String          // required — должен совпадать с разрешённым в личном кабинете
  scope: List<String>         // default: ["profile", "email"]
  state: String?               // auto-generated CSRF token, verified on callback
  prompt: "login" | "consent" | "none"  // optional
  theme: "dark" | "light" | "auto"     // default: "auto" (follows system)
  lang: "ru" | "en" | "zh" | null      // null = system locale
  loginHint: String?           // pre-fill email field
  presentationStyle: "modal" | "fullscreen" | "bottomSheet"  // default: "modal"
  telemetry: Boolean           // default: true — opt-out anytime
}
TpidWidget.Result
TpidWidget.Result {
  success(code: String, state: String)
  error(code: TpidError, message: String, httpStatus: Int?)
}

enum TpidError {
  userCancelled,       // пользователь закрыл виджет
  networkError,        // нет сети / таймаут
  invalidClient,       // неверный client_id или redirect_uri
  invalidScope,        // scope не разрешён для клиента
  rateLimited,         // 429 — слишком частые попытки
  serverError,         // 5xx от TPID
  phishingDetected,    // сорван TLS-pin → возможная MITM-атака
  unknown
}

Вызов — единая строка на каждом SDK

Kotlin / Compose
TpidLoginButton(
    clientId = "tpid_pk_...",
    redirectUri = "cupol://callback",
    scope = listOf("profile", "email"),
    onSuccess = { code, state -> viewModel.exchangeCode(code) },
    onError = { err -> toast(err.message) }
)
Swift / SwiftUI
TPIDLoginButton(
    clientId: "tpid_pk_...",
    redirectURI: "cupol://callback",
    scope: [.profile, .email],
    style: .standard
) { result in
    switch result {
    case .success(let code): await exchange(code)
    case .failure(let err): show(err)
    }
}
Flutter / Dart
TpidLoginButton(
  clientId: 'tpid_pk_...',
  redirectUri: 'cupol://callback',
  scope: const ['profile', 'email'],
  onSuccess: (code, state) => ref.read(auth).exchange(code),
)
C# / WinUI 3 / WPF
await TpidLoginButton.ShowAsync(new TpidConfig {
    ClientId = "tpid_pk_...",
    RedirectUri = "myapp://callback",
    Scope = new[] { "profile", "email" },
    Theme = TpidTheme.Auto
}, onSuccess: (code, state) => ExchangeCode(code));

Экраны и UI-потоки

Виджет показывает 8 экранов в едином стиле на всех платформах. Переход между ними — автоматический, SDK сам принимает решение по ответу /auth/account-check.

#ЭкранНазначениеAPI вызов
1EmailВвод email + «Продолжить»POST /auth/account-check
2ПарольВвод пароля + «Забыли?»POST /auth/login
3Код из email6-значный код + таймерPOST /auth/send-code + /auth/verify
42FA / TOTP6-значный TOTPPOST /auth/2fa/verify
5Passkey / BiometricFaceID / TouchID / Windows HelloWebAuthn через /auth/passkey
6УспехCheck-анимация → callback в приложениеOAuth code/oauth/token
7Регистрацияemail + пароль + имя + кодPOST /auth/register
8Восстановление пароляemail → код → новый парольPOST /auth/forgot-password + /auth/reset-password
A11y и i18n: все экраны поддерживают VoiceOver / TalkBack / Narrator, Dynamic Type, high-contrast, клавиатурную навигацию, RU/EN/ZH + кастомный lang, темы dark/light/auto с system-follow.

Безопасность

  • PKCE S256 обязателен — SDK генерирует code_verifier + code_challenge, приложение никогда не видит пароль.
  • TLS pinning по SPKI для *.tokenpay.space. Пины получаются через GET /sdk/tls-pins, кешируются 24 часа. При mismatch — TpidError.phishingDetected.
  • Anti-phishing badge «Официальный TOKEN PAY ID» отображается на каждом экране, иконка из /sdk/config.
  • Secure storage: refresh_token в Keychain (iOS/macOS), EncryptedSharedPreferences (Android), Credential Manager / DPAPI (Windows), libsecret / KWallet (Linux).
  • Device binding: refresh_token привязан к device-fingerprint — нельзя украсть и использовать на другом устройстве без re-auth.
  • Screenshot prevention: FLAG_SECURE на Android, UIView blur при app-switcher на iOS, блокировка Snipping Tool на Windows.
  • No secret logging: пароль, коды и токены никогда не попадают в логи даже в debug-режиме.
  • Rate-limit UX: при 429 показывается дружелюбный экран с таймером до разблокировки.

Device Flow — TV, watch, игровые консоли, IoT (RFC 8628)

Для устройств без клавиатуры/браузера (Apple TV, Android TV, Xbox, умные часы, ESP32): устройство показывает короткий код, пользователь открывает на телефоне id.tokenpay.space/device и вводит его. После подтверждения устройство получает токен.

POST /oauth/device

POST/api/v1/oauth/device

Инициирует Device Flow. Возвращает device_code (секретный, для polling) и user_code (короткий, показывается пользователю).

ПараметрТипОписание
client_idstringrequired
scopestringЗапрашиваемые разрешения (по умолч. profile email)
JSON — 200 OK
{
  "device_code": "tpid_dc_abc123...",
  "user_code": "ABCD-EFGH",
  "verification_uri": "https://id.tokenpay.space/device",
  "verification_uri_complete": "https://id.tokenpay.space/device?user_code=ABCD-EFGH",
  "expires_in": 600,
  "interval": 5
}

POST /oauth/device/token

POST/api/v1/oauth/device/token

Устройство polling-ит этот endpoint раз в interval секунд до получения access_token.

ПараметрТипОписание
grant_typestringrequiredurn:ietf:params:oauth:grant-type:device_code
device_codestringrequired
client_idstringrequired

Возможные ответы:

ОтветОписание
200 OK + tokensПользователь подтвердил — выданы access/refresh_token
400 authorization_pendingЖдём подтверждения — продолжайте polling
400 slow_downСлишком частые запросы — увеличьте interval на 5s
400 access_deniedПользователь нажал «Отклонить»
400 expired_tokenИстекли 10 минут — запросите новый device_code

POST /oauth/device/verify

POST/api/v1/oauth/device/verify

Вызывается с авторизованной сессии пользователя (обычно со страницы /device). Ассоциирует user_code с пользователем.

ПараметрТипОписание
user_codestringrequired — в формате ABCD-EFGH
actionstringapprove (по умолч.) или deny
Apple TV / tvOS — полный flow (Swift)
// 1. Запросить device_code
let init: DeviceInitResponse = try await api.post("/oauth/device", body: [
    "client_id": clientId, "scope": "profile email"
])
let userCode = init.user_code  // "ABCD-EFGH" — показать на экране ТВ
showOnScreen(code: userCode, url: init.verification_uri)

// 2. Polling каждые `interval` секунд
while true {
    try await Task.sleep(seconds: init.interval)
    let r: TokenOrError = try await api.post("/oauth/device/token", body: [
        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
        "device_code": init.device_code,
        "client_id": clientId
    ])
    switch r {
    case .success(let tokens):
        saveTokens(tokens); return
    case .pending: continue          // authorization_pending
    case .slowDown: init.interval += 5; continue
    case .denied, .expired: throw TPIDError.userCancelled
    }
}

Integration API

GET /sdk/config

GET/api/v1/sdk/config

Полная конфигурация для инициализации native widget: endpoints, supported flows/scopes/auth methods, брендинг, TLS-пины, лимиты. Не требует аутентификации — SDK вызывает при старте.

ПараметрТипОписание
client_idstringОпционально — если указан, добавляется блок branding
JSON — 200 OK (сокращённо)
{
  "provider": "TOKEN PAY ID",
  "version": "2.5.0",
  "endpoints": {
    "authorize": "https://id.tokenpay.space/api/v1/oauth/authorize",
    "token": "https://id.tokenpay.space/api/v1/oauth/token",
    "userinfo": "https://id.tokenpay.space/api/v1/oauth/userinfo",
    "device_authorize": "https://id.tokenpay.space/api/v1/oauth/device",
    "device_token": "https://id.tokenpay.space/api/v1/oauth/device/token",
    "account_check": "https://id.tokenpay.space/api/v1/auth/account-check",
    "telemetry": "https://id.tokenpay.space/api/v1/telemetry/event",
    "openid_config": "https://id.tokenpay.space/.well-known/openid-configuration",
    "jwks": "https://id.tokenpay.space/.well-known/jwks.json"
  },
  "supported_flows": ["authorization_code", "refresh_token", "device_code", "qr_login"],
  "supported_scopes": ["openid", "profile", "email", "offline_access"],
  "supported_auth_methods": ["email_password", "email_code", "totp_2fa", "passkey", "biometric", "magic_link"],
  "supported_themes": ["dark", "light", "auto"],
  "supported_languages": ["ru", "en", "zh"],
  "presentation_styles": ["modal", "fullscreen", "bottomSheet"],
  "pkce": { "required": true, "methods": ["S256"] },
  "tls_pinning": {
    "enabled": true,
    "hosts": ["id.tokenpay.space", "tokenpay.space"],
    "pins_endpoint": "https://id.tokenpay.space/api/v1/sdk/tls-pins"
  },
  "anti_phishing_badge": { "label": "Официальный TOKEN PAY ID", "verify_url": "https://tokenpay.space/verify" },
  "rate_limits": { "auth": { "per_min": 8, "per_day": 1000 } },
  "branding": { "client_name": "CUPOL VPN", "logo_uri": "..." }
}

GET /sdk/version

GET/api/v1/sdk/version

Версии всех официальных SDK, ссылки на пакеты, минимальная поддерживаемая версия API. SDK использует для авто-обновления и breaking-change уведомлений.

JSON — 200 OK
{
  "api": "2.5.0",
  "sdks": {
    "kotlin_android": { "version": "2.6.0", "url": "https://tokenpay.space/sdk/android/" },
    "swift": { "version": "2.6.0", "platforms": ["iOS 15+", "macOS 12+", ...] },
    "jvm_desktop": { "version": "2.6.0", "url": "https://tokenpay.space/sdk/jvm-desktop/" },
    "flutter_dart": { "version": "2.3.0", "status": "spec" },
    "dotnet": { "version": "2.3.0", "status": "spec" },
    "rust": { "version": "2.3.0", "status": "spec" }, ...
  },
  "min_supported": { "api": "2.1.0", "sdk": "2.0.0" },
  "breaking_changes": false
}

GET /sdk/tls-pins

GET/api/v1/sdk/tls-pins

SPKI SHA-256 пины для certificate pinning в native-SDK. SDK должен кешировать их на 24 часа и отказывать в соединении при mismatch (TpidError.phishingDetected).

JSON — 200 OK
{
  "hosts": ["id.tokenpay.space", "tokenpay.space", "api.tokenpay.space"],
  "algorithm": "sha256",
  "format": "base64",
  "pins": ["PRIMARY_SPKI_B64=", "BACKUP_SPKI_B64="],
  "refresh_interval_s": 86400,
  "issued_at": "2026-04-17T13:00:00Z"
}
Ротация сертификатов: всегда храним 2+ пина (текущий + backup) в ответе, чтобы пережить ротацию без обновления SDK. SDK должен принимать соединение если совпал хотя бы один пин.

POST /auth/account-check

POST/api/v1/auth/account-check

Pre-login probe: по email проверяет, зарегистрирован ли пользователь и какие методы авторизации у него доступны. Native widget использует это перед показом экрана пароля — чтобы сразу показать passkey/2FA если есть.

Anti-enumeration: endpoint требует валидный client_id и rate-limit 8/min. Timing uniform — внешний наблюдатель не может отличить несуществующий email от существующего по времени ответа.
ПараметрТипОписание
emailstringrequired
client_idstringrequired — валидируется против active api_keys
JSON — 200 OK
{
  "exists": true,
  "methods": ["email_password", "email_code", "totp_2fa", "passkey", "magic_link"],
  "can_register": false,
  "email_verified": true,
  "has_password": true,
  "has_totp": true,
  "has_passkey": true,
  "suggested_next_step": "passkey"
}
Widget алгоритм
val r = api.accountCheck(email, clientId)
when (r.suggested_next_step) {
    "passkey"   -> showPasskeyScreen()          // FaceID/TouchID/Hello
    "password"  -> showPasswordScreen()         // если нет passkey
    "email_code" -> sendCodeAndShowCodeScreen() // если нет пароля
    "register"  -> showRegistrationScreen()     // exists == false
}

GET /auth/session-version

GET/api/v1/auth/session-version

Позволяет SDK проверить, не была ли сессия инвалидирована (смена пароля / 2FA / logout всех сессий). SDK вызывает этот endpoint при старте приложения — если session_version изменилось, локальные токены отзываются и показывается экран логина.

JSON — 200 OK
{
  "session_version": 7,
  "user_id": "tpid_usr_...",
  "should_reauth": false
}

POST /telemetry/event

POST/api/v1/telemetry/event

Принимает события funnel-аналитики native widget для дашборда интегратора. PII никогда не сохраняется — входной фильтр режет любые поля похожие на email/password/code/token/secret/phone/card/ssn.

ПараметрТипОписание
eventstringrequired — имя события (см. список ниже)
client_idstringДля группировки в дашборде
session_idstringСлучайный UUID сессии widget'а (для funnel)
sdk_versionstringВерсия native SDK
platformstringios, android, windows, macos, linux, flutter, etc.
propertiesobjectСвободные метаданные (только string/number/boolean, не более 200 символов на значение)

Разрешённые события:

// Жизненный цикл widget
widget.opened, widget.closed, widget.cancelled, widget.success

// Пошаговая воронка
widget.step_shown, widget.step_completed, widget.step_error

// Проблемы
widget.network_error, widget.rate_limited, widget.tls_pin_fail

// SDK
sdk.init, sdk.ready

// Device flow
device_flow.code_generated, device_flow.approved,
device_flow.denied, device_flow.expired
Пример (Kotlin)
TpidTelemetry.send(
    event = "widget.step_shown",
    clientId = "tpid_pk_...",
    sessionId = widgetSession.id,
    platform = "android",
    sdkVersion = BuildConfig.SDK_VERSION,
    properties = mapOf(
        "step" to "password",
        "theme" to "dark",
        "lang" to "ru",
        "app_bundle" to "space.cupol.vpn"
    )
)

GET /telemetry/funnel — дашборд для владельца клиента

GET/api/v1/telemetry/funnel

Возвращает агрегированную воронку событий по client_id. Требует auth + проверяет владельца ключа.

GET /api/v1/telemetry/funnel?client_id=tpid_pk_...&since=7%20days
Authorization: Bearer tpid_sk_...

→ { "events": [ { "event": "widget.opened", "count": 12403, "sessions": 9812 },
              { "event": "widget.success", "count": 7204, "sessions": 7204 } ] }

Changelog

v2.6.0 — 21 апреля 2026

Widget UX — "Создать аккаунт" теперь работает на всех платформах

— Кнопка "Создать аккаунт" на Welcome screen во всех 3 SDK (Android / Swift / JVM) теперь ведёт прямо в форму регистрации, а не на Email screen
register_hint — пояснение ru/en/zh, почему виджет перешёл на регистрацию при вводе неизвестного email
— "Отправить код подтверждения" укорочено до "Далее" / "Next" / "下一步" во всех SDK
— Встроенная кнопка самообновления в update-banner: чёрно-белый стиль, открывает remote.download_url в системном браузере
— JVM-виджет: окно сжато до 540 dp, контент прижат к верху — убраны пустые полосы сверху и снизу, на которые жаловались интеграторы

Email шаблоны — один canonical layout, без внешних картинок

— Шапка и подвал писем теперь пара чёрных HTML-полос с белым текстом "TOKEN PAY LLC". Больше никаких битых email-header-black.png / email-footer-black.png, которые gmail рендерил как пустые белые прямоугольники
— Код подтверждения рендерится по цифрам в отдельных <span>, без letter-spacing на контейнере — устранён "фантомная точка" после цифр в Mail.ru/Outlook
— Шрифт почты Arial-first вместо Comfortaa fallback — письмо одинаково выглядит в Gmail Web/iOS/Android, Mail.ru, Yandex Mail, Outlook, Apple Mail

Gradle и совместимость

— Все три SDK скомпилены и упакованы как pre.7; archive checksums обновлены в releases.json
— JVM regression test привязан к SdkBuildInfo.version == "2.6.0"

v2.5.0-pre.1 — 17 апреля 2026

Native Widget v2 — реальные SDK-исходники (по запросу CUPOL VPN)

Android SDK (Kotlin + Jetpack Compose) — полный source release
Swift SDK (SwiftUI, iOS/macOS/tvOS/watchOS/visionOS)
Download hub: tokenpay.space/sdk — версии, checksums, install per platform
GET /api/v1/sdk/releases — manifest c артефактами для авто-обновления
— 9 экранов Native UI (Compose + SwiftUI): email, password, email code, 2FA, passkey, recovery, loading, success, fatal error
— OAuth 2.0 + PKCE S256 (mandatory), TLS pin via OkHttp CertificatePinner / URLSession delegate, EncryptedSharedPreferences / Keychain storage

Integration API

— Новые endpoints: /sdk/config, /sdk/tls-pins, /sdk/version расширенный (10 SDK)
POST /auth/account-check — pre-login probe с уведомлением о доступных методах (password/passkey/TOTP)
GET /auth/session-version — SDK проверяет инвалидацию сессий
— Device Flow (RFC 8628): /oauth/device + /oauth/device/token + /oauth/device/verify + /oauth/device/info
— Telemetry API: /telemetry/event (с PII-фильтром: email/password/code/token блокируются) + /telemetry/funnel (дашборд для владельца client_id)
— Спецификация API-контракта TpidWidget.Config / Result / Error для всех SDK
— Secure storage recommendations per platform (Keychain / EncryptedSharedPreferences / DPAPI / libsecret)

Дистрибуция: pre-release, source по NDA через dev@tokenpay.space. Maven Central / SwiftPM registry — Q2 2026.

Инфраструктура

— CORS: fix HTTP 500 на OPTIONS preflight (ломал Android/iOS/Windows native HTTP клиенты)
/api/v1/health + /api/v1/ping (HEAD/GET) — heartbeat для SDK
— JSON 404 handler вместо HTML Express default
— SSE stream heartbeat 25s для обхода Cloudflare idle timeout

Документация

— Закрытый API: обновлены главная и docs — API выдаётся по запросу, не публичный
— Swift SDK полная секция: iOS + iPadOS + macOS + tvOS + watchOS + Linux/Vapor
— Linux Desktop: Python/Bash/GTK/Qt 4 варианта интеграции
— Raw HTTP — универсальный 3-шаговый способ для любой платформы
— Language/theme toggles с поддержкой RU/EN/ZH на /docs

v2.3.0 — 13 апреля 2026

SDK-спецификации (design docs; реальные SDK выпущены с v2.5.0-pre.1, см. выше)

— Kotlin / Android — дизайн API
— Swift / iOS — дизайн API
— C# / .NET — спецификация (реализация Q2 2026)
— PHP / Laravel Socialite — спецификация
— Ruby / OmniAuth — спецификация
— Dart / Flutter — спецификация
— Rust — спецификация

Desktop & Native

— Документация по интеграции: Electron, Tauri, WPF, SwiftUI
— Поддержка ?theme= и ?lang= параметров для OAuth страниц
— Deep link flow с custom URL schemes
— Localhost callback для CLI-утилит

UI

— Чёрные звёзды-частицы в белой теме (инверсия тёмной)
— Частицы добавлены на все страницы экосистемы
— Обновлён favicon.ico (мультиразмерный из нового логотипа)
— Cache busting обновлён до v=11

v2.2.0 — 27 марта 2026

Безопасность

— PKCE (S256) теперь обязателен для всех OAuth-клиентов
— Ротация refresh-токенов: при обновлении выдаётся новый refresh_token
session_version — смена пароля/2FA мгновенно инвалидирует все сессии
— API-ключи запрещены из браузерных окружений (только серверный вызов)
requireScope middleware для гранулярного контроля доступа
— Webhook подписи в формате Stripe: t=timestamp,v1=hmac
— Отвязка TPID доступна только администратору
/.well-known/security.txt (RFC 9116)

Dynamic Client Registration (RFC 7591)

POST /oauth/register — программная регистрация OAuth-клиентов
GET /oauth/client-info/:id — получение метаданных клиента
PUT /oauth/register/:id — обновление метаданных клиента
— Consent screen отображает tos_uri, policy_uri, client_uri
— OpenID Discovery: registration_endpoint, client_info_endpoint

QR-вход

POST /auth/qr/login-init — создание QR-сессии
GET /auth/qr/login-poll/:id — polling статуса
POST /auth/qr/login-confirm/:id — подтверждение с мобильного
— Исправлен критический баг: QR-токены теперь корректно проходят авторизацию

API

— Строгая валидация redirect_uri (exact match)
— OpenID Discovery: pkce_required: true, code_challenge_methods_supported: ["S256"]
— API версия обновлена до 2.2.0

SDK

tokenpay-auth.js обновлён до v2.0.0
— Автоматическая генерация PKCE (S256) в authorize()
getCodeVerifier() — получение code_verifier из sessionStorage
— CSRF state validation на callback
code_verifier автоматически передаётся в exchangeCode()
— Кнопки обновлены: текст «TOKEN PAY ID», шрифт Comfortaa

v2.1.0 — 18 марта 2026

OAuth 2.0

— Поддержка prompt=login для принудительной переавторизации
— Поддержка login_hint для предзаполнения email
— Повторная валидация redirect_uri в /oauth/approve
— Новый эндпойнт: GET /oauth/branding

Widget SDK v1.2.0

— 3 варианта кнопок: standard, icon, logo
TPID.loginWithOAuth() — OAuth popup flow с Promise API
— Автоматический рендер через data-tpid-button
— Поддержка тем dark/light/auto

Нужна помощь?

Свяжитесь с нашей командой разработчиков