Справочник API

Вход и личный кабинет

Регистрация, вход, подтверждение почты, профиль, адреса доставки и устройства. Нужно, если вы делаете свою витрину: гость проходит путь до оплаты без входа, но история заказов, избранное и бонусы — уже за входом. Общее для всего раздела: любой эндпоинт с заголовком X-CSRF-Token может ответить 403 {"error":"Неверный или отсутствующий CSRF-токен"} либо 403 {"error":"Недопустимый Origin"} — это не описано у каждого эндпоинта отдельно, чтобы не повторяться.

Как устроен вход

После входа платформа ставит две куки: короткоживущую для доступа и долгую для продления. Токен в теле ответа не возвращается намеренно — так его не сможет прочитать посторонний скрипт на странице.

Все запросы отправляйте с credentials: "include". Если витрина и API на разных доменах, без этого браузер не приложит куки, и вход будет «слетать» на каждой перезагрузке.

Ключ для форм

Любой изменяющий запрос требует заголовка X-CSRF-Token — возьмите его один раз через GET /auth/csrf и держите в памяти приложения. Это защита от того, чтобы чужой сайт отправил запрос от имени вашего покупателя, пользуясь его куками.

Ответ 403 с упоминанием CSRF почти всегда означает, что ключ устарел: возьмите новый и повторите запрос.

Что делать при 401

Короткий доступ живёт недолго, и рано или поздно любой запрос вернёт 401. Правильная реакция: вызвать POST /auth/refresh и повторить исходный запрос. Если и refresh ответил 401 — сессии больше нет, показывайте форму входа. Повторять его в цикле бессмысленно.

Покупатель принадлежит магазину

Регистрация привязывает человека к тому магазину, где он зарегистрировался. Один и тот же адрес почты может быть заведён в разных магазинах платформы — это разные учётные записи с разной историей заказов.

Поэтому заголовок X-Site-Source обязателен при регистрации, входе и восстановлении пароля.

вход
const csrf = await fetch("https://api.amarix.ru/auth/csrf", {
  credentials: "include",
}).then((r) => r.json());

await fetch("https://api.amarix.ru/auth/login", {
  method: "POST",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    "X-Site-Source": "acme",
    "X-CSRF-Token": csrf.csrfToken,
  },
  body: JSON.stringify({ email, password }),
});

Регистрация, вход, подтверждение почты, профиль, адреса доставки и устройства. Нужно, если вы делаете свою витрину: гость проходит путь до оплаты без входа, но история заказов, избранное и бонусы — уже за входом. Общее для всего раздела: любой эндпоинт с заголовком X-CSRF-Token может ответить 403 {"error":"Неверный или отсутствующий CSRF-токен"} либо 403 {"error":"Недопустимый Origin"} — это не описано у каждого эндпоинта отдельно, чтобы не повторяться.

GET/auth/csrf

Ключ для форм

Одноразовый ключ, без которого не пройдёт ни один изменяющий запрос.

Доступ:Без авторизации

Возьмите его перед первым POST и держите в памяти приложения. Платформа проверяет его у всех методов, кроме чтения: так чужой сайт не сможет отправить запрос от имени вашего покупателя, даже если браузер приложит его куки.

Поля ответа

ПолеТипОписание
csrfTokenstringКладите его в заголовок X-CSRF-Token.
ответ
{
  "csrfToken": "b5f1…"
}
POST/auth/register

Регистрация

Заводит покупателя и сразу выполняет вход.

Доступ:Без авторизации

Магазин берётся из заголовка витрины: покупатель принадлежит тому магазину, где зарегистрировался. Один и тот же адрес почты может быть заведён в разных магазинах платформы — это разные люди с точки зрения данных.

Заголовки

ПараметрТипОбязателенОписание
X-Site-SourcestringдаКод магазина. Без него регистрация отклоняется.
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
emailstringдаПочта покупателя.
passwordstringдаПароль.
lastNamestringдаФамилия.
firstNamestringдаИмя.
middleNamestringнетОтчество.
phonestringнетТелефон.
agreedToMarketingbooleanнетСогласие на рассылку. Отдельное от согласия на обработку данных.
initDatastringнетДанные Telegram, если регистрация идёт из мини-приложения.

Поля ответа

ПолеТипОписание
userobjectПрофиль нового покупателя.
ответ
{
  "user": {
    "id": 4821,
    "email": "kupil@example.com",
    "lastName": "Иванов",
    "firstName": "Иван",
    "middleName": null,
    "phone": null,
    "agreedToMarketing": false,
    "emailVerified": false,
    "storefrontBrandKey": "acme",
    "createdAt": "2026-09-02T10:00:00.000Z"
  }
}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Пользователь с такой почтой уже существует"}В этом магазине уже есть покупатель с таким адресом.Предложите войти или восстановить пароль.
400{"error":"Регистрация покупателя доступна только на витрине бренда"}Запрос пришёл без заголовка витрины.Добавьте X-Site-Source.
400{"error":"Этот Telegram уже привязан к другому аккаунту"}initData ссылается на Telegram-аккаунт, уже привязанный к другому покупателю.—
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов на регистрацию/вход с одного адреса за 15 минут (успешные не считаются).—
POST/auth/login

Вход

Проверяет пару почта-пароль и ставит куки сессии.

Доступ:Без авторизации

Ответ содержит профиль, а сам доступ приходит куками: браузер приложит их сам, если вы шлёте запросы с credentials: "include". Токен в теле не возвращается намеренно — так его не сможет прочитать посторонний скрипт на странице.

Заголовки

ПараметрТипОбязателенОписание
X-Site-SourcestringнетКод магазина. Формально не обязателен — без него запрос не отклоняется, но резолвится в служебный контекст персонала, а не покупателя магазина. Для витрины передавайте всегда.
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
emailstringдаПочта.
passwordstringдаПароль.
initDatastringнетДанные Telegram для входа из мини-приложения.

Поля ответа

ПолеТипОписание
userobjectПолный профиль покупателя — тот же набор полей, что у GET /auth/profile.
ответ
{
  "user": {
    "id": 4821,
    "email": "kupil@example.com",
    "lastName": "Иванов",
    "firstName": "Иван",
    "middleName": null,
    "phone": "+79990000000",
    "telegram": null,
    "telegramId": null,
    "avatarUrl": null,
    "agreedToMarketing": false,
    "emailVerified": true,
    "mustChangePassword": false,
    "bonusBalance": 0,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-01-01T00:00:00.000Z",
    "roles": ["customer"],
    "permissions": [],
    "allBrands": false,
    "brandIds": [1],
    "brandCodes": ["acme"],
    "hasSiteAccess": false
  }
}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
401{"error":"Неверный email или пароль"}Пара не сошлась либо покупателя нет в этом магазине.Один и тот же текст на оба случая — это сделано намеренно, чтобы нельзя было перебором узнать, кто у вас зарегистрирован.
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов на вход/регистрацию с одного адреса за 15 минут (успешные не считаются).—
POST/auth/telegram-login

Вход через Telegram

Вход или регистрация по initData мини-приложения — без пароля.

Доступ:Без авторизации

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
initDatastringдаДанные Telegram Web App, подписанные ботом.

Поля ответа

ПолеТипОписание
userobjectПолный профиль покупателя — тот же набор полей, что у GET /auth/profile.

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Требуется initData от Telegram"}Тело пустое или initData не строка.—
401{"error":"<текст ошибки проверки подписи>"}initData не проходит проверку подписи бота или просрочена.—
POST/auth/refresh

Продлить сессию

Обновляет короткоживущий доступ по долгой куке.

Доступ:Сессия сотрудника

Вызывайте, когда любой запрос вернул 401, и повторяйте исходный запрос. Тело не нужно — всё берётся из кук. Проверка не через обычный auth-миддлварь: маршрут проверяет refresh-куку вручную в самом контроллере, поэтому здесь нет привычного 401 {"error":"Unauthorized"}.

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.
ответ
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
401{"error":"Refresh token не предоставлен"}Долгой куки нет вообще.Показывайте форму входа.
401{"error":"Сессия недействительна"}Кука есть, но сессия не проходит проверку (отозвана, не совпадает с записью на сервере).Показывайте форму входа.
401{"error":"<текст ошибки из jwt-библиотеки>"}Долгая кука просрочена или повреждена — текст берётся напрямую из ошибки проверки токена (jwt expired, invalid signature и т.п.), поэтому не постоянный.Показывайте форму входа.
POST/auth/logout

Выход

Гасит текущую сессию и стирает куки.

Доступ:Без авторизации

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.
ответ
{"message":"Вы успешно вышли из системы"}
POST/auth/verify-email

Подтвердить почту

Принимает код из письма.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
codestringдаКод из письма.
ответ
{
  "user": {
    "id": 4821,
    "email": "kupil@example.com",
    "emailVerified": true
  }
}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Неверный код подтверждения"}Код не совпал.—
400{"error":"Срок действия кода истёк"}Код просрочен.Запросите новый через /auth/resend-verification.
400{"error":"Пользователь не найден"}Покупателя с этой сессией больше нет.—
POST/auth/resend-verification

Выслать код заново

Отправляет новый код подтверждения почты.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.
ответ
{"message":"Код подтверждения отправлен"}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Email уже подтвержден"}Почта подтверждена раньше.—
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов с одного адреса за 15 минут (успешные не считаются).Покажите таймер и дайте повторить позже.
POST/auth/forgot-password

Забыли пароль

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

Доступ:Без авторизации

Ответ одинаковый независимо от того, есть такой покупатель или нет: иначе форму можно использовать, чтобы узнать, кто у вас зарегистрирован.

Заголовки

ПараметрТипОбязателенОписание
X-Site-SourcestringнетКод магазина. Формально не обязателен — без него запрос резолвится в служебный контекст, а не в контекст магазина. Для витрины передавайте всегда.
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
emailstringдаПочта.
ответ
{"message":"Если аккаунт с такой почтой существует, на неё отправлен код"}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов с одного адреса за 15 минут.—
POST/auth/reset-password

Задать новый пароль

Меняет пароль по коду из письма.

Доступ:Без авторизации

Заголовки

ПараметрТипОбязателенОписание
X-Site-SourcestringнетКод магазина. Формально не обязателен — без него запрос резолвится в служебный контекст, а не в контекст магазина. Для витрины передавайте всегда.
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
emailstringдаПочта.
codestringдаКод из письма.
newPasswordstringдаНовый пароль. Имя поля именно newPassword, не password.
ответ
{"message":"Пароль успешно изменён"}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Неверный email или код"}Такого покупателя нет либо код выписан не ему.—
400{"error":"Неверный код"}Код не совпал.—
400{"error":"Срок действия кода истёк"}Код просрочен.—
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов с одного адреса за 15 минут.—
POST/auth/change-password

Сменить пароль

Меняет пароль, когда покупатель уже вошёл.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
currentPasswordstringдаДействующий пароль.
newPasswordstringдаНовый пароль.
confirmPasswordstringдаПовтор нового пароля — должен совпасть с newPassword.
ответ
{"message":"Пароль успешно изменён"}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Заполните все поля"}Какое-то из трёх полей пустое.—
400{"error":"Пароли не совпадают"}newPassword и confirmPassword разошлись.—
400{"error":"Пароль должен быть не менее 8 символов"}Проверка длины на уровне контроллера.—
400{"error":"Текущий пароль неверный"}currentPassword не совпадает с действующим.—
400{"error":"Пароль должен содержать минимум 8 символов"}Повторная проверка длины на уровне сервиса.—
400{"error":"Нельзя использовать временный пароль. Придумайте новый."}newPassword совпадает с временным паролем, который выдала платформа.—
400{"error":"Новый пароль должен отличаться от текущего"}newPassword совпадает с currentPassword.—
POST/auth/change-email-request

Запрос смены почты

Отправляет код на новый адрес.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
newEmailstringдаНовый адрес.
ответ
{"message":"Код подтверждения отправлен на новый email"}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Email уже используется"}Адрес занят другим покупателем этого магазина.—
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов с одного адреса за 15 минут.—
POST/auth/change-email-confirm

Подтвердить смену почты

Применяет новый адрес по коду.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
codestringдаКод с нового адреса.
ответ
{
  "user": {
    "id": 4821,
    "email": "novaya@example.com",
    "emailVerified": true
  }
}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
400{"error":"Запрос на смену email не найден"}Смену не запрашивали или запрос устарел.—
400{"error":"Неверный код подтверждения"}Код не совпал.—
400{"error":"Срок действия кода истёк"}Код просрочен.—
429{"error":"Слишком много попыток. Попробуйте через 15 минут."}Больше 30 запросов с одного адреса за 15 минут.—
GET/auth/profile

Профиль

Данные покупателя и его бонусный счёт.

Доступ:Сессия сотрудника

Заодно самый простой способ понять при загрузке страницы, вошёл человек или нет: 200 — вошёл, 401 — нет.

Поля ответа

ПолеТипОписание
idintИдентификатор покупателя.
emailstringПочта.
lastNamestringФамилия.
firstNamestringИмя.
middleNamestringОтчество.
phonestringТелефон.
telegramstringИмя в Telegram, если привязан.
avatarUrlstringСсылка на аватар.
emailVerifiedbooleanПодтверждена ли почта.
agreedToMarketingbooleanСогласие на рассылку.
bonusBalanceintБонусный счёт.бонусы, 1 бонус = 1 рубль
createdAtstringКогда зарегистрирован.
updatedAtstringКогда профиль правили в последний раз.
telegramIdstring | nullID Telegram-аккаунта, если привязан.
mustChangePasswordbooleanПлатформа выдала временный пароль — покупателя нужно подвести к смене.
rolesstring[]Роли покупателя. Для обычного покупателя — ["customer"].
permissionsstring[]Права, если это сотрудник, а не покупатель.
allBrandsbooleanДоступ ко всем брендам платформы — только для персонала.
brandIdsnumber[]Идентификаторы брендов, к которым привязан аккаунт.
brandCodesstring[]Коды тех же брендов.
hasSiteAccessbooleanДоступ к админке площадки — только для персонала.

Ошибки

КодОтвет сервераКогда возникаетЧто делать
401{"error":"Unauthorized"}Не вошёл или сессия истекла.Попробуйте /auth/refresh, затем повторите.
PUT/auth/profile

Изменить профиль

Правит имя, телефон и согласие на рассылку.

Доступ:Сессия сотрудника

Тело запроса

ПараметрТипОбязателенОписание
lastNamestringнетФамилия.
firstNamestringнетИмя.
middleNamestringнетОтчество.
phonestringнетТелефон.
telegramstringнетИмя в Telegram — правится и отдельно от привязки аккаунта.
agreedToMarketingbooleanнетСогласие на рассылку.
ответ
{
  "id": 4821,
  "email": "kupil@example.com",
  "lastName": "Иванов",
  "firstName": "Иван",
  "middleName": null,
  "phone": "+79990000000",
  "telegram": null,
  "telegramId": null,
  "avatarUrl": null,
  "agreedToMarketing": false,
  "emailVerified": true,
  "bonusBalance": 0,
  "updatedAt": "2026-09-02T11:00:00.000Z"
}
GET/auth/addresses

Адреса доставки

Сохранённые адреса покупателя.

Доступ:Сессия сотрудника

Поля ответа

ПолеТипОписание
idintИдентификатор адреса.
typestringpvz — пункт выдачи, home — курьером.
tagstringКак покупатель назвал адрес: «Дом», «Работа».
citystringГород.
cityCodeintКод города у перевозчика.
streetstringУлица.
housestringДом.
apartmentstringКвартира.
entrancestringПодъезд.
floorstringЭтаж.
intercomstringДомофон.
postalCodestringИндекс.
pvzCodestringКод пункта выдачи.
pvzAddressstringАдрес пункта выдачи.
countrystringСтрана. По умолчанию «Россия».
isDefaultbooleanПодставлять по умолчанию.
userIdintИдентификатор владельца адреса.
createdAtstringКогда адрес добавлен, ISO-дата.
updatedAtstringКогда адрес правили в последний раз, ISO-дата.
POST/auth/addresses

Добавить адрес

Сохраняет адрес в кабинете.

Доступ:Сессия сотрудника

Тело запроса

ПараметрТипОбязателенОписание
typepvz | homeдаПункт выдачи или курьер.
citystringдаГород.
cityCodeintнетКод города у перевозчика — берите из /orders/cities.
tagstringнетНазвание адреса.
streetstringнетУлица (для курьера).
housestringнетДом.
apartmentstringнетКвартира.
entrancestringнетПодъезд.
floorstringнетЭтаж.
intercomstringнетДомофон.
postalCodestringнетИндекс.
pvzCodestringнетКод пункта выдачи — из /orders/pvzs/{cityCode}.
pvzAddressstringнетАдрес пункта выдачи.
countrystringнетСтрана. По умолчанию «Россия», если не передать.
isDefaultbooleanнетСделать основным.
ответ
{
  "id": 33,
  "userId": 4821,
  "type": "home",
  "tag": "Дом",
  "city": "Москва",
  "cityCode": 44,
  "street": "Ленина",
  "house": "1",
  "apartment": "5",
  "entrance": "2",
  "floor": "3",
  "intercom": "1234",
  "postalCode": "101000",
  "pvzCode": null,
  "pvzAddress": null,
  "country": "Россия",
  "isDefault": true,
  "createdAt": "2026-09-01T10:00:00.000Z",
  "updatedAt": "2026-09-01T10:00:00.000Z"
}
PUT/auth/addresses/{id}

Изменить адрес

Правит сохранённый адрес.

Доступ:Сессия сотрудника

Параметры пути

ПараметрТипОбязателенОписание
idintдаИдентификатор адреса.

Тело запроса

ПараметрТипОбязателенОписание
typepvz | homeнетПункт выдачи или курьер.
citystringнетГород.
cityCodeintнетКод города у перевозчика.
tagstringнетНазвание адреса.
streetstringнетУлица (для курьера).
housestringнетДом.
apartmentstringнетКвартира.
entrancestringнетПодъезд.
floorstringнетЭтаж.
intercomstringнетДомофон.
postalCodestringнетИндекс.
pvzCodestringнетКод пункта выдачи.
pvzAddressstringнетАдрес пункта выдачи.
countrystringнетСтрана.
isDefaultbooleanнетСделать основным.
ответ
{
  "id": 33,
  "userId": 4821,
  "type": "home",
  "tag": "Дом",
  "city": "Москва",
  "cityCode": 44,
  "street": "Ленина",
  "house": "1",
  "apartment": "5",
  "entrance": "2",
  "floor": "3",
  "intercom": "1234",
  "postalCode": "101000",
  "pvzCode": null,
  "pvzAddress": null,
  "country": "Россия",
  "isDefault": true,
  "createdAt": "2026-09-01T10:00:00.000Z",
  "updatedAt": "2026-09-01T11:00:00.000Z"
}

Ошибки

КодОтвет сервераКогда возникаетЧто делать
404{"error":"Address not found"}Адреса нет или он принадлежит другому покупателю. Сообщение на английском — так отвечает контроллер, не переведено.—
DELETE/auth/addresses/{id}

Удалить адрес

Убирает адрес из кабинета.

Доступ:Сессия сотрудника

Параметры пути

ПараметрТипОбязателенОписание
idintдаИдентификатор адреса.

Ошибки

КодОтвет сервераКогда возникаетЧто делать
500{"error":"Internal server error"}Адреса нет или он чужой. Баг бэкенда — по коду это неотличимо от настоящего сбоя сервера, см. notes.—
GET/auth/sessions

Устройства

Где сейчас выполнен вход.

Доступ:Сессия сотрудника
ответ
{
  "sessions": [
    {
      "id": "sess_a1b2c3",
      "deviceName": "Chrome, Windows",
      "ipAddress": "1.2.3.4",
      "userAgent": "Mozilla/5.0 ...",
      "isCurrent": true,
      "createdAt": "2026-08-01T10:00:00.000Z",
      "lastSeenAt": "2026-09-02T09:00:00.000Z",
      "expiresAt": "2026-09-30T10:00:00.000Z"
    }
  ],
  "currentSessionId": "sess_a1b2c3"
}
DELETE/auth/sessions/{id}

Отключить устройство

Гасит одну сессию.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Параметры пути

ПараметрТипОбязателенОписание
idstringдаИдентификатор сессии.

Ошибки

КодОтвет сервераКогда возникаетЧто делать
404{"error":"Сессия не найдена"}Сессии нет или она принадлежит другому покупателю.—
POST/auth/sessions/revoke-all

Выйти на всех устройствах

Гасит все сессии, кроме текущей.

Доступ:Сессия сотрудника

Заголовки

ПараметрТипОбязателенОписание
X-CSRF-TokenstringдаКлюч из /auth/csrf.

Тело запроса

ПараметрТипОбязателенОписание
exceptCurrentbooleanнетпо умолчанию trueОставить текущую сессию живой. false гасит вообще все, включая ту, из которой пришёл запрос.
ответ
{"revokedCount":3}

Рядом: корзина и оформление — путь покупателя до оплаты, который проходится и без входа.