Yord ID для разработчиков
Вход через Yord ID — стандартный OpenID Connect (Authorization Code + PKCE). Подключается любой OIDC-библиотекой по одному адресу.
1. Получить client_id
Напишите на info@yord.tech: название продукта и адреса возврата (redirect_uri). Пришлём
client_id и client_secret. Мобильное или SPA-приложение — публичный клиент: без секрета,
только PKCE; redirect_uri вида space.yord.app:/callback. Адрес возврата сверяется посимвольно.
Адрес после выхода (post_logout_redirect_uri) тоже должен быть в этом списке.
2. Адреса
discovery https://id.yord.space/.well-known/openid-configuration authorize https://id.yord.space/authorize token https://id.yord.space/token userinfo https://id.yord.space/userinfo jwks https://id.yord.space/jwks logout https://id.yord.space/logout
3. Отправить пользователя на вход
GET https://id.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.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.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.yord.space, aud = ваш client_id, nonce).
5. Данные пользователя
В id_token и в GET https://id.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.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.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. Документы пользователя
Паспорт, СНИЛС, ИНН и другие документы, проверенные сотрудником Yord Tech. Только продуктам с секретом,
которым Yord Tech подключил нужные документы (юрлицо, ИНН и цель указываются при подключении).
scope: passport, passport_registration, foreign_passport, foreign_id,
snils, inn, oms, driver_license, address.
Человек видит экран согласия с вашим юрлицом, ИНН, перечнем и целью; согласие действует 1 год.
POST https://id.yord.space/api/documents
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
token=<access_token>
200 OK
{"passport": {"verified_at": "2026-10-14T09:00:00+00:00", "verified_by": "Yord Tech",
"fields": {"last_name": "…", "first_name": "…", "number": "…", …}},
"address": [{"verified_at": "…", "verified_by": "Yord Tech", "fields": {…}}]}
Отдаются только документы со статусом verified. ФИО — из самого документа, не из профиля.
Согласованный, но непроверенный документ в ответ не попадает, поэтому ответ может быть и {}. Документов нет в id_token и /userinfo. Запросы — только с сервера, 60 в минуту на продукт.
Тело — строго application/x-www-form-urlencoded; JSON и multipart читаются как пустое тело и дают 401 invalid_token.
Ошибки — JSON {"error": "…", "error_description": "…"}:
401 invalid_client нет Basic, неверный секрет, продукт отключён или публичный 401 invalid_token токен не at+jwt, выдан другому продукту, истёк, человек заблокирован 403 consent_required нет действующего согласия хотя бы на один документный scope токена (или в токене их нет) — отказ на весь запрос, частичной выдачи нет; согласия нет, оно отозвано или изменились адреса возврата (redirect_uri), владелец, юрлицо, ИНН, цель — отправьте человека на /authorize 429 rate_limited больше 60 запросов в минуту 503 temporarily_unavailable документы временно выключены или недоступны (в том числе не читается сохранённый документ) — повторите позже
После отзыва согласия продукт обязан удалить полученные данные (условие соглашения с Yord Tech).
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.yord.space.
- Аутентификация —
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 постоянный предел превышен (сумма выше предела продукта или согласия) 422 launch_limit_exceeded первую неделю работы Yord Pay платёж не больше 10 000 ₽ (намерение, предупреждение, списание) 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 — превышен постоянный предел
(например, сумма выше предела продукта): повтор не поможет, измените запрос. launch_limit_exceeded —
временный предел первой недели боевого режима: снимется сам через неделю после запуска; этот ответ не сохраняется,
повтор с тем же ключом после снятия предела пройдёт.
expires_at ограничивает только начало оплаты: итог — только succeeded или canceled.
Если заказ уже отменён, а платёж прошёл — сделайте возврат. Возвращению человека на return_url не верьте: статус
узнавайте запросом GET /api/v1/payments/{id}.
Автоплатёж. Списание по согласию — только после предупреждения: charge_at не раньше чем через 24 часа,
списывать можно не раньше charge_at и не позже чем через двое суток после него, на сумму не больше указанной
в предупреждении, и не раньше чем через 24 часа после доставки предупреждения. delivered: true значит, что
письмо или сообщение в MAX поставлено в очередь на отправку, а не что человек его прочитал; 24 часа считаются от фактической доставки.
Если почта или MAX недоступны дольше 15 минут, сообщение снимается с очереди недоставленным: списание по такому
предупреждению получит 409 notice_required — отправьте новое предупреждение (новый Idempotency-Key).
Без российского канала у человека (почта на российском домене или MAX) предупреждение не доставляется — notice_undeliverable. Закрывающий чек по
предоплате пробивается в день оказания услуги.
Уведомления продукту (если задан адрес): POST {"event": "payment.succeeded", "id": "…"} — также
payment.canceled, refund.succeeded, refund.canceled, refund.review, payment.disputed, mandate.revoked,
payment.close_overdue. Данных в теле нет: перезапросите объект через API.
13. Удаление аккаунта
Когда человек удаляет аккаунт Yord ID, продукты, куда он входил, получают об этом событие. Адрес events_uri
(https, без данных в пути) продукт сообщает при подключении; без него события не отправляются.
POST <events_uri>
Content-Type: application/secevent+jwt
<JWT: iss=https://id.yord.space, aud=<ваш client_id>, iat, jti, sub=<идентификатор человека>,
events={"https://id.yord.space/events/account-deleted": {}}>
Продукт проверяет: подпись по ключам https://id.yord.space/jwks, iss, aud равен своему client_id,
заголовок typ = secevent+jwt, iat не старше 7 суток, в events есть событие удаления аккаунта. Затем удаляет все данные по
sub и отвечает 202; при неверном токене — 400 без подробностей.
Любой ответ не из 2xx считается неудачей: Yord ID повторяет доставку с паузой 1, 2, 4… минут (до часа) в течение 7 суток.
Поэтому обработка должна быть идемпотентной: событие по уже удалённому sub тоже получает 202.