Войти через RouterAI: приложение получает API-ключ в один клик по OAuth PKCE
Чтобы подключить стороннее приложение к RouterAI, раньше нужно было зайти в кабинет, создать API-ключ, скопировать его и вставить в настройки приложения. Теперь хватает одной кнопки «Войти через RouterAI». Пользователь подтверждает выдачу ключа на нашей странице, и приложение получает его само.
Работает это по стандарту OAuth 2.0 с PKCE. Регистрировать приложение у нас не нужно, а адреса и поля совпадают с OpenRouter. Ниже показываем, как это выглядит для пользователя и как добавить такую кнопку в свой продукт.
Как это выглядит для пользователя
Для статьи мы написали маленькое веб-приложение «Конспект»: оно пересказывает длинный текст в трёх пунктах. Своего ключа у приложения нет, оно работает на балансе пользователя.

Кнопка ведёт на страницу RouterAI. Здесь видно, какое приложение просит ключ и на какой адрес пользователь вернётся. Он выбирает аккаунт, личный или командный, название ключа и лимит расходов.

После «Выдать ключ» пользователь возвращается в приложение, и оно уже подключено. Ключ он не видел, не копировал и никуда не вставлял.

Что остаётся под контролем пользователя
Приложение получает обычный API-ключ, но на условиях пользователя:
- Лимит расходов. Приложение может предложить своё значение, а решает пользователь. Если приложение лимит не указало, в форме стоит 100 ₽ в день. Когда лимит исчерпан, запросы отклоняются.
- Аккаунт. Расходы идут с баланса того аккаунта, который выбран на экране согласия.
- Отзыв в любой момент. Ключ появляется в настройках ключей с пометкой «OAuth» и адресом приложения. Там можно изменить лимит, выключить ключ или удалить его.

Пересказ из нашего примера стоил 3 копейки, и они сразу видны в расходах ключа.
Если отклонить запрос, приложение ничего не получит: пользователь вернётся в него с отметкой об отказе.
Как добавить кнопку в своё приложение
Весь обмен состоит из двух шагов: отправить пользователя на /auth и обменять полученный код на ключ. Ниже код из нашего «Конспекта», он работает в браузере без сервера и без библиотек.
Шаг 1. Отправляем пользователя в RouterAI. Приложение придумывает случайную строку code_verifier и оставляет её у себя. В адрес уходит только её хеш, code_challenge.
const ROUTERAI = 'https://routerai.ru';
const CALLBACK = location.origin + location.pathname;
const b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const random = () => b64url(crypto.getRandomValues(new Uint8Array(32)));
async function login() {
const verifier = random();
const state = random();
const challenge = b64url(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));
sessionStorage.pkce = JSON.stringify({ verifier, state });
location.assign(`${ROUTERAI}/auth?` + new URLSearchParams({
callback_url: CALLBACK,
code_challenge: challenge,
code_challenge_method: 'S256',
state,
app_name: 'Конспект',
key_label: 'Конспект',
limit: 50,
limit_period: 'month',
}));
}
app_name показывается на экране согласия. key_label, limit и limit_period подставляются в форму как значения по умолчанию, пользователь может их изменить.
Шаг 2. Меняем код на ключ. RouterAI возвращает пользователя на callback_url с параметрами code и state. Приложение сверяет state и отправляет код вместе с code_verifier:
async function handleCallback() {
const query = new URLSearchParams(location.search);
if (!query.has('code') && !query.has('error')) return;
history.replaceState(null, '', location.pathname); // код не должен остаться в адресной строке
const { verifier, state } = JSON.parse(sessionStorage.pkce || '{}');
sessionStorage.removeItem('pkce');
if (query.get('state') !== state) return fail('Ответ пришёл не на наш запрос.');
if (query.get('error')) return fail('Вы отклонили выдачу ключа.');
const response = await fetch(`${ROUTERAI}/api/v1/auth/keys`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: query.get('code'), code_verifier: verifier, callback_url: CALLBACK }),
});
if (!response.ok) return fail('Не удалось получить ключ.');
const { key } = await response.json(); // обычный ключ sk-…
}
Дальше ключ работает как любой другой: адрес https://routerai.ru/api/v1, заголовок Authorization: Bearer sk-…, те же модели и те же запросы.
Что важно учесть:
-
callback_urlдолжен быть наhttps. Обычныйhttpразрешён только дляlocalhostи127.0.0.1, так удобно разрабатывать. Мобильное или десктопное приложение может указать собственную схему, напримерmyapp://callback. - При обмене
callback_urlпередаётся тот же, что и в/auth, символ в символ. - Код действует 5 минут и срабатывает один раз.
- Храните ключ как пароль. В веб-приложении с сервером надёжнее делать обмен на сервере и не отдавать ключ в браузер.
Примеры на Python, Node.js и Bash, все параметры и коды ошибок собраны в руководстве по OAuth PKCE.
Если у вас уже есть «Login with OpenRouter»
Пути и названия полей мы сделали такими же, как у OpenRouter: /auth для согласия и /api/v1/auth/keys для обмена. Приложению, которое уже умеет входить через OpenRouter, достаточно заменить хост на https://routerai.ru.
При переносе проверьте три вещи. Поддерживается только метод S256, без plain. Лимит задаётся в рублях. В ответе на обмен приходит только key, без идентификатора пользователя.
Для консольных программ: вход без callback
У CLI-утилиты или скрипта нет адреса, на который можно вернуть пользователя. Для них есть режим без callback_url: программа открывает в браузере ту же страницу /auth, а после подтверждения RouterAI показывает код на экране.

Пользователь вставляет код в программу, и она обменивает его на ключ тем же запросом, только без callback_url.
Что сделано для безопасности
-
Перехваченный код бесполезен. Чтобы обменять его на ключ, нужен
code_verifier, а он не покидал приложение. В этом и состоит смысл PKCE. -
Повторное использование кода выключает ключ. Если уже использованный код предъявить ещё раз вместе с верным
code_verifier, значит, утекла вся пара. Выданный по ней ключ перестаёт работать. - Ошибки показываем у себя. Если в запросе приложения что-то не так, пользователь видит сообщение на странице RouterAI. На чужой адрес он попадает только после своего решения, выдать ключ или отклонить.
-
Повторный вход не плодит ключи. Когда пользователь снова подключает то же приложение к тому же аккаунту, ключ перевыпускается: старый секрет перестаёт работать, статистика сохраняется. Веб-приложение узнаётся по адресу, локальное и консольное — по
app_name. - Название приложения — это только подпись. Его присылает само приложение, проверить его мы не можем. Поэтому на экране согласия отдельно показан адрес возврата: ключ получит именно он.
Чего пока нет
- У выданного ключа нет срока действия. Он работает, пока пользователь его не выключит или не удалит.
- Приложение получает только ключ. Имя и почту пользователя RouterAI не передаёт.
С чего начать
Если вы делаете приложение, которое работает с моделями, добавьте в него кнопку «Войти через RouterAI». Регистрация и согласование не нужны: возьмите код из примера выше и подставьте свой callback_url. Подробности — в руководстве по OAuth PKCE.
Если вы пользуетесь таким приложением, проверьте на экране согласия адрес возврата и поставьте лимит, с которым вам спокойно.