Yord ID для разработчиков
Вход через Yord ID — стандартный OpenID Connect (Authorization Code + PKCE). Подключается любой OIDC-библиотекой по одному адресу.
Песочница: тестовые данные, адреса ниже — песочницы. Код подтверждения — 000000.
1. Получить client_id
В песочнице клиента создаёте вы сами. Войдите: код подтверждения всегда 000000,
писем и СМС нет; или нажмите «Создать тестового пользователя» на экране входа. Затем откройте
Разработчику и введите код приглашения от Yord Tech (info@yord.tech). До 10 клиентов;
секрет показывается один раз. Адреса возврата — https или http://localhost:<порт>/… и
http://127.0.0.1:<порт>/… (только здесь). Клиенты и аккаунты, которыми не пользовались
30 дней, удаляются. Токены песочницы на боевом Yord ID недействительны; перенос клиента в боевой — по письму
на info@yord.tech.
2. Адреса
discovery https://id-sandbox.yord.space/.well-known/openid-configuration authorize https://id-sandbox.yord.space/authorize token https://id-sandbox.yord.space/token userinfo https://id-sandbox.yord.space/userinfo jwks https://id-sandbox.yord.space/jwks logout https://id-sandbox.yord.space/logout
3. Отправить пользователя на вход
GET https://id-sandbox.yord.space/authorize?response_type=code &client_id=<client_id> &redirect_uri=https://продукт/auth/yord/callback &scope=openid profile email phone &state=<случайная строка — сверить на возврате> &nonce=<необязательно; если передали — сверьте в id_token> &code_challenge=<BASE64URL(SHA256(code_verifier))> &code_challenge_method=S256 [&prompt=login — спросить вход, даже если пользователь уже вошёл] [&login_methods=phone,vk,yandex — какие способы входа показать] [&require=phone — без подтверждённого телефона кода не будет]
Если пользователь уже вошёл в Yord ID в этом браузере, он сразу вернётся в продукт — без экрана входа.
На возврате: ?code=…&state=…, при ошибке — ?error=…&error_description=…&state=….
login_methods — способы входа через запятую, которые увидит человек на экране входа Yord ID:
phone (телефон через бота Telegram или MAX), email (код на почту), vk,
ok, mail, yandex, telegram, google, apple.
Неизвестные значения пропускаются; если не осталось ни одного или параметра нет — показываются все способы.
Скрытым способом войти в рамках этого запроса нельзя, но человек, который уже вошёл в Yord ID, вернётся
в продукт сразу: аккаунт один, способ входа — только ключ к нему (кроме prompt=login). На экране
входа есть ссылка «Отменить вход» — продукт получит error=access_denied.
require — phone, email или оба через запятую; каждому нужен такой же scope
(require=phone без scope phone — ошибка invalid_request). Если в аккаунте
нет подтверждённого контакта, Yord ID попросит добавить его (телефон — через бота, почту — кодом) и только потом
вернёт код. Отказался — вернётся error=access_denied. Если вход в Yord ID был больше 10 минут назад,
перед добавлением контакта человек входит ещё раз — любым своим способом, login_methods здесь
не действует.
GET https://id-sandbox.yord.space/authorize?…&scope=openid phone&login_methods=phone,vk&require=phone
отмена: https://продукт/auth/yord/callback?error=access_denied
&error_description=Пользователь не добавил телефон&state=…
4. Обменять код на токены (с сервера продукта)
POST https://id-sandbox.yord.space/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<code>&redirect_uri=<тот же>&code_verifier=<verifier>
&client_id=<client_id>&client_secret=<secret> (или Authorization: Basic)
200 OK
{"access_token": "eyJ…", "token_type": "Bearer", "expires_in": 900,
"id_token": "eyJ…", "refresh_token": "…", "scope": "email openid profile"}
Код живёт 60 секунд и срабатывает один раз. id_token проверяйте по ключам из jwks
(RS256, iss = https://id-sandbox.yord.space, aud = ваш client_id, nonce).
5. Данные пользователя
В id_token и в GET https://id-sandbox.yord.space/userinfo с заголовком
Authorization: Bearer <access_token>:
sub постоянный ID пользователя во всех продуктах — храните его name, picture scope profile given_name, family_name, middle_name, birthdate scope profile (отчество и дата — если указаны; дата YYYY-MM-DD) yord_identities scope profile: ["vk", "telegram", …] — чем человек может входить email, email_verified scope email (почта всегда подтверждена) phone_number, phone_number_verified scope phone
6. Обновить токены
POST /token grant_type=refresh_token&refresh_token=<…>&client_id=…&client_secret=…
refresh_token живёт 30 дней и одноразовый: в ответе — новый. Повтор старого отзывает всю цепочку — пользователю придётся войти заново.
7. Выход
GET https://id-sandbox.yord.space/logout?client_id=<client_id>&post_logout_redirect_uri=<адрес из списка>&state=<…>
Если передаёте id_token_hint, отправляйте запрос методом POST (поля формы): так токен не попадёт в URL и логи.
8. Ошибки /token
400 invalid_grant код или refresh_token недействителен, использован, истёк; не тот redirect_uri или verifier 400 unsupported_grant_type только authorization_code и refresh_token 401 invalid_client неизвестный client_id или неверный секрет 429 rate_limited больше 30 запросов в минуту на клиента с одного IP
9. Примеры
Python, модуль oauth_ru (тот же, что для VK ID и Яндекс ID):
cfg = oauth_ru.Config(client_id=..., client_secret=..., scope='openid profile email phone',
redirect_uri='https://продукт/auth/yord/callback')
url = oauth_ru.authorize_url('yord', cfg, state, challenge) # → редирект
token = await oauth_ru.exchange('yord', cfg, cb, verifier, client) # на callback
profile = oauth_ru.normalize('yord', await oauth_ru.fetch_profile('yord', cfg, token, client))
oauth_ru пока не передаёт nonce: защиту от подмены даёт PKCE + state.
Node, Auth.js:
providers: [{ id: 'yord', name: 'Yord ID', type: 'oidc', issuer: 'https://id-sandbox.yord.space',
clientId: process.env.YORD_CLIENT_ID, clientSecret: process.env.YORD_CLIENT_SECRET,
authorization: { params: { scope: 'openid profile email phone' } } }]
iOS / Android: AppAuth с discovery-адресом выше; вход открывается в системном браузере (ASWebAuthenticationSession / Custom Tabs), не во встроенном WebView.
10. Документы
В песочнице документов нет: документные scope не выдаются. Описание — на боевом Yord ID.
11. Кнопка
<a class="yord-id-btn" href="/auth/yord/start">
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true"><rect width="24" height="24" rx="7" fill="#fff"/>
<path d="M7 6.5h2.6L12 10.6l2.4-4.1H17l-3.7 6v5h-2.6v-5L7 6.5Z" fill="#05607A"/></svg>
<span>Войти через Yord ID</span></a>
<style>
.yord-id-btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; box-sizing: border-box;
height: 48px; min-width: 240px; padding: 0 20px; border-radius: 12px; background: #05607A; color: #fff;
font: 600 15px/1 -apple-system, "Segoe UI", Roboto, sans-serif; text-decoration: none; }
.yord-id-btn:hover { background: #034458; }
.yord-id-btn:focus-visible { outline: 3px solid #2BA3C4; outline-offset: 2px; }
</style>
Высота — не меньше 40 px, текст — «Войти через Yord ID», цвет и значок не менять. Ссылка ведёт на маршрут продукта, который начинает вход (шаг 3).
12. Yord Pay: оплата картой из Yord ID
Подключается по договору: Yord Tech включает продукту оплату и автоплатежи. Карту человек вводит только на странице
ЮKassa; продукт видит только API Yord Pay на https://pay-sandbox.yord.space.
В песочнице оплата — заглушка без денег: confirm_url ведёт на страницу с кнопками
«Оплатить», «Отклонить» и «Оплатить с сохранением карты» (последняя — если человеку можно сохранить карту).
Статусы, уведомления продукту и чеки («чек пробит») идут тем же путём, что в бою. Включите scope
pay у своего серверного клиента в «Разработчику».
- Аутентификация —
Authorization: Basic client_id:client_secret; тело — JSON до 64 КБ; не больше 120 запросов в минуту. - Суммы — строкой в рублях с двумя знаками:
"1990.00". - Все POST и DELETE — с заголовком
Idempotency-Keyдо 64 символов, уникальным на 7 дней. descriptionиorder_ref— до 64 символов; передавать в них персональные данные (почты, телефоны, ФИО) запрещено.order_ref— только латиница, цифры и знаки. _ : -(без пробелов).items— от 1 до 100 позиций{"description" (до 128 символов), "quantity" (1–1000), "price", "vat_code" (1–12; для своего продукта — код из договора), "payment_subject" (commodity | job | service | payment | intellectual_activity | property_right | another), "payment_mode" (full_payment | full_prepayment)}; сумма price × quantity по всем позициям равнаamount. Автоплатёж — толькоfull_payment.mandate(необязательно, нужен scopepay_recurringв токене) —{"title" (до 64 символов), "max_amount", "period" (week | month | year), "max_per_period"}.- Ошибки:
{"error": "…", "message": "…"}.
POST /api/v1/intents — намерение: token (access-токен человека со scope pay), amount, description,
order_ref, items, return_url, [mandate] → 201 intent_id, confirm_url, expires_at
GET /api/v1/payments/{id} — статус: id, status (pending | succeeded | canceled), amount, refunded_amount, ...
POST /api/v1/payments/{id}/close — закрывающий чек по предоплате: items [{index, quantity}] → receipt_id, status
POST /api/v1/mandates/{id}/notices — предупреждение: amount, charge_at → notice_id, delivered
POST /api/v1/charges — автоплатёж: mandate_id, notice_id, amount, description, items → 202 payment_id
DELETE /api/v1/mandates/{id} — отменить подписку → id, revoked, inflight
POST /api/v1/refunds — возврат: payment_id, amount, [items], reason → refund_id, status
GET /api/v1/refunds/{id} — статус возврата
Коды ошибок (поле error) и HTTP-статусы:
401 invalid_client нет Basic или неверный секрет, продукт отключён или публичный 401 invalid_token access_token человека недействителен или человек заблокирован 403 insufficient_scope оплата или автоплатежи продукту не подключены, в токене нет scope pay / pay_recurring, Yord Pay пока открыт только сотрудникам Yord Tech 400 invalid_request неверное тело, сумма, позиции, return_url, charge_at, нет Idempotency-Key 400 mandate_too_large предел списания больше утверждённого тарифа продукта 404 not_found платёж, согласие или возврат не найден (чужой объект тоже 404) 409 mandate_inactive аккаунт заблокирован, получатель изменился или результат прошлого списания неизвестен 409 method_inactive карта согласия больше не действует 409 notice_required нет действующего предупреждения для этого списания 409 notice_undeliverable предупреждение не доставлено: у человека нет российского канала 409 refund_blocked возврат запрещён: спор, возврат с неизвестным результатом и т. п. 409 idempotency_conflict этот Idempotency-Key уже использован с другим запросом 409 idempotency_in_progress запрос с этим ключом ещё выполняется 422 limit_exceeded постоянный предел превышен (сумма выше предела продукта или согласия) 429 limit_exceeded временный лимит (запросов, оплат за час или сутки, списаний за период)
Повторы и идемпотентность. Сохраняются только ответы 2xx и детерминированные 4xx: повтор с тем же ключом и тем же
телом вернёт тот же ответ. Ответ 5xx и 429 не сохраняются — их можно повторить с тем же Idempotency-Key.
Ответ 2xx или детерминированный 4xx (в том числе 409 notice_required, mandate_inactive)
запоминается за ключом на 7 дней и повторяется как есть: исправив причину 4xx, отправляйте запрос с новым Idempotency-Key.
idempotency_conflict — тот же ключ с другим запросом (не сохраняется); idempotency_in_progress —
первый запрос с этим ключом ещё выполняется: повторите позже с тем же ключом.
429 и 422. 429 — временный лимит: повторите позже с нарастающей паузой. 422 — превышен постоянный предел (например, сумма выше предела продукта): повтор не поможет, измените запрос.
expires_at ограничивает только начало оплаты: итог — только succeeded или canceled.
Если заказ уже отменён, а платёж прошёл — сделайте возврат. Возвращению человека на return_url не верьте: статус
узнавайте запросом GET /api/v1/payments/{id}.
Автоплатёж. Списание по согласию — только после предупреждения: charge_at не раньше чем через 24 часа,
списывать можно не раньше charge_at и не позже чем через двое суток после него, на сумму не больше указанной
в предупреждении, и не раньше чем через 24 часа после доставки предупреждения. delivered: true значит, что
письмо поставлено в очередь на отправку, а не что человек его прочитал; 24 часа считаются от фактической доставки.
Без российского канала у человека предупреждение не доставляется — notice_undeliverable. Закрывающий чек по
предоплате пробивается в день оказания услуги.
Уведомления продукту (если задан адрес): POST {"event": "payment.succeeded", "id": "…"} — также
payment.canceled, refund.succeeded, refund.canceled, refund.review, payment.disputed, mandate.revoked,
payment.close_overdue. Данных в теле нет: перезапросите объект через API.