Личный кабинет
Документация · v1

API FernieID
для разработчиков

Единая система авторизации, баланса и AI для всех проектов FernieX. Один API-ключ — доступ к аутентификации через Telegram-бота, балансам пользователей и AI-модели Aurin. Ниже — всё, что нужно, чтобы подключить своё приложение за 10 минут.

01 Введение

FernieID — это система авторизации и баланса, общая для всех проектов FernieX: Telegram-бота, AI-чата, игровой платформы, Minecraft-сервера и сторонних приложений разработчиков. API-ключ даёт вашему приложению доступ ко всем этим возможностям через один REST-интерфейс.

Что умеет API

  • Аутентификация — вход пользователей FernieID с двухфакторным подтверждением через Telegram.
  • Баланс — чтение и изменение баланса пользователя (DC, семена и другие валюты бота).
  • AI-чат — доступ к модели Aurin (на базе Mistral) с потоковыми ответами и автоматическим поиском в интернете.
  • Уведомления — отправка сообщений пользователю в Telegram от имени вашего приложения.
  • Веб-поиск — обёртка над поисковой выдачей для использования в своих ботах и приложениях.
Базовый URL API: https://ferniex-id.vercel.app — все примеры ниже используют этот адрес.

02 Быстрый старт

Путь от нуля до первого запроса — четыре шага.

1

Зарегистрируйся в FernieID

Создай аккаунт в личном кабинете и привяжи Telegram — без него не будут работать 2FA и уведомления.

2

Создай API-ключ

Раздел «API-ключи» → «Создать ключ» → укажи название и тип приложения. Ключ формата fid_... выдаётся один раз, скопируй его сразу.

3

Подключи SDK или делай запросы напрямую

Для веба проще всего подключить fernieid-sdk.js — он берёт на себя окно логина, 2FA и обёртки над баланс/AI эндпоинтами. Для бэкенда и ботов — просто вызывай REST-эндпоинты.

4

Проверь ключ

Сделай тестовый запрос на /api/apikeys/verify — если вернулся success: true, всё готово.

03 Получение API-ключа

Ключи создаются и управляются в личном кабинете FernieID, раздел «API-ключи». Никакого отдельного консольного доступа — всё через тот же аккаунт, которым пользуется человек.

Типы приложений

При создании ключа нужно выбрать тип — это влияет только на отображение в списке и внутреннюю аналитику, на права доступа не влияет.

Веб
Мобайл
Бот
Игра
Другое
Пополнение баланса (action: "add" в /api/edit/balance) доступно только ключам с типом «Бот». Остальные типы могут только списывать (spend).

Лимиты

  • Максимум 10 ключей на один аккаунт (для администраторов сайта лимит снят).
  • Полный текст ключа показывается только при создании — дальше он маскируется в интерфейсе.
  • Каждый ключ можно переименовать, изменить тип и приложение, посмотреть логи запросов или удалить.
Ключ равнозначен паролю приложения. Не публикуй его в клиентском JS-коде для чувствительных операций (изменение баланса) — используй его только с бэкенда, если это не встроенный SDK-поток входа.

04 Подключение SDK

Для браузерных приложений проще всего подключить готовый скрипт fernieid-sdk.js — он рисует окно входа с 2FA, хранит сессию в localStorage и даёт удобные методы для баланса и AI.

html
<script src="/sdk/fernieid-sdk.js"></script>
<script>
  FernieID.init('fid_твой_ключ_здесь');
</script>

Открыть окно входа

Метод возвращает промис, который резолвится данными пользователя после успешного входа (включая прохождение 2FA, если Telegram привязан).

javascript
const user = await FernieID.login();
// { success: true, userId, username, role, telegramLinked }
console.log('Вошёл как', user.username);

Методы SDK — справочник

FernieID.init(apiKey: string)
Инициализация SDK с твоим API-ключом. Вызывается один раз при загрузке страницы, до любых других методов.
FernieID.login() → Promise<User>
Показывает модальное окно логина/регистрации с 2FA. Резолвится объектом пользователя после успешного входа.
FernieID.getUser() → User | null
Возвращает текущего вошедшего пользователя из localStorage без похода на сервер.
FernieID.logout()
Очищает сохранённую сессию пользователя.
FernieID.getBalance(fields?: string[]) → Promise
Получает баланс текущего пользователя. По умолчанию запрашивает ['dc','seeds','balance']. Если Telegram не привязан — вернёт ссылку на привязку.
FernieID.editBalance(currency, amount, action) → Promise
action"add" (только для бот-ключей) или "spend". currency"dc" или "seeds".
FernieID.chat(messages, options?) → Promise
Разовый (не потоковый) запрос к AI Aurin. Возвращает полный текст ответа и статистику по токенам.
FernieID.streamChat(messages, callbacks, options?) → Promise<void>
Потоковый запрос к AI. callbacks: { onToken, onDone, onError }.
FernieID.getChatUsage() → Promise
Расход токенов текущего пользователя за сегодня и суточный лимит.
FernieID.search(query) → Promise
Веб-поиск через встроенный прокси (Serper / DuckDuckGo фолбэк).

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

Вход происходит по логину и паролю FernieID. Если у пользователя привязан Telegram, сервер сразу отправляет 6-значный код и ожидает подтверждения — это обязательный второй фактор, обойти его нельзя.

POST /api/auth/login

Проверяет логин/пароль. Если Telegram привязан — отправляет код и возвращает require2fa: true, вход завершается только после /api/auth/verify-2fa. Если Telegram не привязан — пускает сразу.

Параметры тела запроса
ПолеТипОписание
apiKeyобязателенstringТвой API-ключ
usernameобязателенstringЛогин FernieID
passwordобязателенstringПароль

Значения username и password берутся из полей формы логина на твоей странице — их вводит сам пользователь, в коде они не прописываются заранее.

html
<input id="loginUsername" type="text" placeholder="Логин">
<input id="loginPassword" type="password" placeholder="Пароль">
<button id="loginBtn">Войти</button>
javascript
const API_KEY = 'fid_твой_ключ'; // хранится на бэкенде, не в клиентском коде
document.getElementById('loginBtn').onclick = async () => {
  // значения берём из того, что реально ввёл пользователь на странице
  const username = document.getElementById('loginUsername').value.trim();
  const password = document.getElementById('loginPassword').value;
  if (!username || !password) return alert('Заполни логин и пароль');
  const res = await fetch('https://ferniex-id.vercel.app/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ apiKey: API_KEY, username, password })
  });
  const data = await res.json();
  // { success: true, require2fa: true, userId: 42, telegramLinked: true }
  if (data.require2fa) {
    // показываем пользователю поле для ввода кода из Telegram
    showCodeInput(data.userId);
  }
};
typescript
const API_KEY: string = 'fid_твой_ключ'; // хранится на бэкенде, не в клиентском коде
interface LoginResponse {
  success: boolean;
  require2fa?: boolean;
  userId?: number;
  telegramLinked?: boolean;
}
document.getElementById('loginBtn')!.addEventListener('click', async () => {
  const username = (document.getElementById('loginUsername') as HTMLInputElement).value.trim();
  const password = (document.getElementById('loginPassword') as HTMLInputElement).value;
  if (!username || !password) return alert('Заполни логин и пароль');
  const res = await fetch('https://ferniex-id.vercel.app/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ apiKey: API_KEY, username, password })
  });
  const data: LoginResponse = await res.json();
  if (data.require2fa) {
    showCodeInput(data.userId!);
  }
});
python
import requests
API_KEY = "fid_твой_ключ"
def login(username: str, password: str) -> dict:
    res = requests.post(
        "https://ferniex-id.vercel.app/api/auth/login",
        json={"apiKey": API_KEY, "username": username, "password": password},
    )
    data = res.json()
    # { "success": True, "require2fa": True, "userId": 42, "telegramLinked": True }
    if data.get("require2fa"):
        # дальше передай data["userId"] в verify_2fa()
        pass
    return data
Строки 'fernie' и '••••••••' из старого примера — это то, что попадёт в переменные из полей формы. Захардкоженных значений в реальном коде быть не должно, логин и пароль всегда приходят от пользователя.

06 Подтверждение 2FA

Код приходит в Telegram-бот пользователя, действителен 5 минут, одноразовый.

POST /api/auth/verify-2fa

Финальный шаг входа. При успехе возвращает полные данные пользователя, а в Telegram улетает уведомление об успешном входе.

Параметры тела запроса
ПолеТипОписание
apiKeyобязателенstringТвой API-ключ
userIdобязателенnumberИз ответа /api/auth/login
codeобязателенstring6-значный код из Telegram

Функция showCodeInput(userId) из предыдущего шага показывает поле для кода — значение code берётся из него, когда пользователь ввёл цифры и нажал подтвердить.

html
<input id="twoFaCode" maxlength="6" placeholder="000000">
<button id="confirmCodeBtn">Подтвердить</button>
javascript
function showCodeInput(userId) {
  document.getElementById('confirmCodeBtn').onclick = async () => {
    // код берём из того, что пользователь реально ввёл в поле
    const code = document.getElementById('twoFaCode').value.trim();
    if (code.length !== 6) return alert('Введи 6-значный код из Telegram');
    const res = await fetch('https://ferniex-id.vercel.app/api/auth/verify-2fa', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ apiKey: API_KEY, userId, code })
    });
    const data = await res.json();
    // { success: true, userId: 42, username: 'fernie', role: 'user' }
    if (data.success) {
      // сохраняем сессию, открываем личный кабинет и т.д.
    }
  };
}
typescript
interface VerifyResponse {
  success: boolean;
  userId?: number;
  username?: string;
  role?: string;
}
function showCodeInput(userId: number): void {
  document.getElementById('confirmCodeBtn')!.addEventListener('click', async () => {
    const code = (document.getElementById('twoFaCode') as HTMLInputElement).value.trim();
    if (code.length !== 6) return alert('Введи 6-значный код из Telegram');
    const res = await fetch('https://ferniex-id.vercel.app/api/auth/verify-2fa', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ apiKey: API_KEY, userId, code })
    });
    const data: VerifyResponse = await res.json();
    if (data.success) {
      // сохраняем сессию, открываем личный кабинет и т.д.
    }
  });
}
python
def verify_2fa(user_id: int, code: str) -> dict:
    res = requests.post(
        "https://ferniex-id.vercel.app/api/auth/verify-2fa",
        json={"apiKey": API_KEY, "userId": user_id, "code": code},
    )
    data = res.json()
    # { "success": True, "userId": 42, "username": "fernie", "role": "user" }
    return data
Код одноразовый — после использования удаляется из базы сразу, повторно применить нельзя. Значение '482913' из старого примера было просто иллюстрацией формата — в реальном коде так вставлять код нельзя, он всегда приходит из поля ввода.

07 Проверка API-ключа

Используется, чтобы убедиться, что ключ валиден и активен — например, перед открытием окна логина.

POST /api/apikeys/verify

Заодно увеличивает счётчики requests_total / requests_today у ключа и пишет запись в его лог.

Параметры тела запроса
ПолеТипОписание
keyобязателенstringAPI-ключ для проверки
javascript
const data = await fetch('https://ferniex-id.vercel.app/api/apikeys/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'fid_твой_ключ' })
}).then(r => r.json());
// { success: true, userId, name, appName, type }
typescript
interface VerifyKeyResponse {
  success: boolean;
  userId?: number;
  name?: string;
  appName?: string;
  type?: string;
}
const data: VerifyKeyResponse = await fetch('https://ferniex-id.vercel.app/api/apikeys/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'fid_твой_ключ' })
}).then(r => r.json());
python
import requests
data = requests.post(
    "https://ferniex-id.vercel.app/api/apikeys/verify",
    json={"key": "fid_твой_ключ"},
).json()
# { "success": True, "userId": ..., "name": ..., "appName": ..., "type": ... }

08 Получить баланс

Читает баланс пользователя напрямую из Telegram-бота (DC, семена, игровая валюта и т.д.) — данные всегда актуальны на момент запроса.

POST /api/balance/get

Пользователь должен быть найден по username и иметь привязанный Telegram — иначе вернётся ошибка no_telegram со ссылкой на привязку.

Параметры тела запроса
ПолеТипОписание
apiKeyобязателенstringТвой API-ключ
usernameобязателенstringЛогин пользователя FernieID
fieldsопциональноstring[]Например ['dc','seeds']. По умолчанию — все известные поля.
javascript
const data = await fetch('https://ferniex-id.vercel.app/api/balance/get', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    username: 'fernie',
    fields: ['dc', 'seeds']
  })
}).then(r => r.json());
// { success: true, username: 'fernie', dc: 4200, seeds: 15300 }
typescript
interface BalanceResponse {
  success: boolean;
  username?: string;
  dc?: number;
  seeds?: number;
}
const data: BalanceResponse = await fetch('https://ferniex-id.vercel.app/api/balance/get', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    username: 'fernie',
    fields: ['dc', 'seeds']
  })
}).then(r => r.json());
python
data = requests.post(
    "https://ferniex-id.vercel.app/api/balance/get",
    json={
        "apiKey": "fid_твой_ключ",
        "username": "fernie",
        "fields": ["dc", "seeds"],
    },
).json()
# { "success": True, "username": "fernie", "dc": 4200, "seeds": 15300 }

09 Изменить баланс

Списывает или начисляет валюту через бота. Все операции логируются в историю ключа.

POST /api/edit/balance
Параметры тела запроса
ПолеТипОписание
apiKeyобязателенstringТвой API-ключ
usernameобязателенstringЛогин пользователя
currencyобязателенstring"dc" или "seeds"
amountобязателенnumberСумма операции
actionобязателенstring"add" (только тип «Бот») или "spend"
javascript
const data = await fetch('https://ferniex-id.vercel.app/api/edit/balance', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_бот_ключ',
    username: 'fernie',
    currency: 'dc',
    amount: 500,
    action: 'add'
  })
}).then(r => r.json());
typescript
interface EditBalanceResponse {
  success: boolean;
  dc?: number;
  seeds?: number;
}
const data: EditBalanceResponse = await fetch('https://ferniex-id.vercel.app/api/edit/balance', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_бот_ключ',
    username: 'fernie',
    currency: 'dc',
    amount: 500,
    action: 'add'
  })
}).then(r => r.json());
python
data = requests.post(
    "https://ferniex-id.vercel.app/api/edit/balance",
    json={
        "apiKey": "fid_твой_бот_ключ",
        "username": "fernie",
        "currency": "dc",
        "amount": 500,
        "action": "add",
    },
).json()
Начисление (add) работает только если ключ создан с типом «Бот». Для веб- и других типов ключей сервер вернёт ошибку.

10 AI Chat — разовый запрос

Доступ к модели Aurin — ассистенту FernieX-AI поверх Mistral, с автоматическим веб-поиском и чтением ссылок «под капотом». Внешне модель никогда не раскрывает свою базовую платформу.

POST /api/chat
Параметры тела запроса
ПолеТипОписание
apiKeyобязателенstringТвой API-ключ
messagesобязателенarray[{ role: 'user', content: '...' }]
modelопциональноstringaurin-lite / mistral-medium / aurin / aurin-pro / aurin-custom
messages[0]опциональноobjectСистемный промпт передаётся как сообщение { role: 'system', content: '...' } первым элементом массива messages. Учитывается для всех моделей, но по-настоящему нужен только для aurin-custom — см. раздел 13.
userIdопциональноnumberID пользователя FernieID — даёт больший дневной лимит токенов вместо анонимного
max_tokensопциональноnumberПо умолчанию 8192
streamопциональноbooleanfalse для этого эндпоинта
javascript
const data = await fetch('https://ferniex-id.vercel.app/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    model: 'aurin-lite',
    messages: [{ role: 'user', content: 'Привет! Что нового в мире технологий?' }]
  })
}).then(r => r.json());
// { choices: [{ message: { role, content } }], usage: { total_tokens } }
typescript
interface ChatResponse {
  choices: { message: { role: string; content: string } }[];
  usage: { total_tokens: number };
}
const data: ChatResponse = await fetch('https://ferniex-id.vercel.app/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    model: 'aurin-lite',
    messages: [{ role: 'user', content: 'Привет! Что нового в мире технологий?' }]
  })
}).then(r => r.json());
python
data = requests.post(
    "https://ferniex-id.vercel.app/api/chat",
    json={
        "apiKey": "fid_твой_ключ",
        "model": "aurin-lite",
        "messages": [{"role": "user", "content": "Привет! Что нового в мире технологий?"}],
    },
).json()
# { "choices": [{ "message": { "role", "content" } }], "usage": { "total_tokens" } }
Модель сама решает, когда нужен поиск в интернете или чтение конкретной страницы — вызывать эти инструменты отдельно не нужно, всё происходит на сервере прозрачно для клиента.

11 AI Chat — потоковый ответ

Тот же эндпоинт /api/chat, но с stream: true — ответ приходит частями по SSE (Server-Sent Events), удобно для «печатающегося» текста в UI.

javascript
const res = await fetch('https://ferniex-id.vercel.app/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    model: 'aurin',
    stream: true,
    messages: [{ role: 'user', content: 'Расскажи историю в 3 предложениях' }]
  })
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop();
  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    const raw = line.slice(6).trim();
    if (raw === '[DONE]') continue;
    const delta = JSON.parse(raw).choices?.[0]?.delta?.content;
    if (delta) process.stdout.write(delta); // или dom.textContent += delta
  }
}
typescript
const res = await fetch('https://ferniex-id.vercel.app/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    model: 'aurin',
    stream: true,
    messages: [{ role: 'user', content: 'Расскажи историю в 3 предложениях' }]
  })
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer: string = '';
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop()!;
  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    const raw = line.slice(6).trim();
    if (raw === '[DONE]') continue;
    const delta = JSON.parse(raw).choices?.[0]?.delta?.content;
    if (delta) console.log(delta);
  }
}
python
import json
import requests
with requests.post(
    "https://ferniex-id.vercel.app/api/chat",
    json={
        "apiKey": "fid_твой_ключ",
        "model": "aurin",
        "stream": True,
        "messages": [{"role": "user", "content": "Расскажи историю в 3 предложениях"}],
    },
    stream=True,
) as res:
    for line in res.iter_lines(decode_unicode=True):
        if not line or not line.startswith("data: "):
            continue
        raw = line[6:].strip()
        if raw == "[DONE]":
            continue
        delta = json.loads(raw)["choices"][0]["delta"].get("content")
        if delta:
            print(delta, end="", flush=True)

С SDK то же самое пишется в несколько строк:

javascript
await FernieID.streamChat(
  [{ role: 'user', content: 'Привет!' }],
  {
    onToken: chunk => console.log(chunk),
    onDone:  full  => console.log('Готово:', full),
    onError: msg   => console.error(msg)
  }
);

12 Лимиты токенов

У AI-чата суточные лимиты в токенах, привязанные к пользователю FernieID (или к самому API-ключу для анонимных запросов).

КатегорияЛимит в сутки
Обычный пользователь FernieID200 000 токенов
Пользователь с подпиской Fernie+1 500 000 токенов
Анонимный запрос (без userId, только по ключу)50 000 токенов на ключ
GET /api/chat/usage/:userId

Возвращает расход токенов за сегодня и остаток лимита.

javascript
const data = await fetch(`https://ferniex-id.vercel.app/api/chat/usage/${userId}`)
  .then(r => r.json());
// { success: true, used: 12400, limit: 200000, has_plus: false, remaining: 187600 }
typescript
interface UsageResponse {
  success: boolean;
  used: number;
  limit: number;
  has_plus: boolean;
  remaining: number;
}
const data: UsageResponse = await fetch(`https://ferniex-id.vercel.app/api/chat/usage/${userId}`)
  .then(r => r.json());
python
data = requests.get(
    f"https://ferniex-id.vercel.app/api/chat/usage/{user_id}"
).json()
# { "success": True, "used": 12400, "limit": 200000, "has_plus": False, "remaining": 187600 }

13 Aurin-Custom — своя персона

Отдельная модель для случаев, когда готовые персоны Aurin не подходят и приложению нужен ассистент под своим именем, характером и стилем ответа. Вместо системного промпта FernieX-AI по умолчанию модель получает ровно тот промпт, который передашь ты.

Чем отличается от остальных моделей

  • Работает на базе Aurin-medium, как и Aurin-medium.
  • Не подставляет собственную персону («Ты — Aurin…») — вместо неё используется системное сообщение, которое ты передашь первым элементом messages.
  • Не несёт зашитых скиллов вроде netsearch/webfetch — если они нужны, добавляй инструкции сам.
POST /api/chat

Тот же эндпоинт, что и в разделе 10 — просто передай model: "aurin-custom" и свой system.

Параметры тела запроса
ПолеТипОписание
apiKeyобязателенstringТвой API-ключ
messagesобязателенarray[{ role: 'user', content: '...' }]
modelобязателенstringЗначение "aurin-custom"
messages[0]опциональноobjectПередай { role: 'system', content: '...' } первым элементом messages — имя, роль, характер и формат ответа, всё, что должна соблюдать модель
javascript
const data = await fetch('https://ferniex-id.vercel.app/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    model: 'aurin-custom',
    messages: [
      { role: 'system', content: 'Тебя зовут Марта. Ты дружелюбный консультант книжного магазина, отвечаешь коротко и с лёгким юмором.' },
      { role: 'user', content: 'Посоветуй что почитать вечером' }
    ]
  })
}).then(r => r.json());
// { choices: [{ message: { role: 'assistant', content: '...' } }], usage: {...} }
typescript
interface ChatResponse {
  choices: { message: { role: string; content: string } }[];
  usage: { total_tokens: number };
}
const data: ChatResponse = await fetch('https://ferniex-id.vercel.app/api/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    apiKey: 'fid_твой_ключ',
    model: 'aurin-custom',
    messages: [
      { role: 'system', content: 'Тебя зовут Марта. Ты дружелюбный консультант книжного магазина, отвечаешь коротко и с лёгким юмором.' },
      { role: 'user', content: 'Посоветуй что почитать вечером' }
    ]
  })
}).then(r => r.json());
python
data = requests.post(
    "https://ferniex-id.vercel.app/api/chat",
    json={
        "apiKey": "fid_твой_ключ",
        "model": "aurin-custom",
        "messages": [
            {"role": "system", "content": "Тебя зовут Марта. Ты дружелюбный консультант книжного магазина, отвечаешь коротко и с лёгким юмором."},
            {"role": "user", "content": "Посоветуй что почитать вечером"},
        ],
    },
).json()
# { "choices": [{ "message": { "role": "assistant", "content": "..." } }], "usage": {...} }
Без сообщения с role: 'system' модель отвечает от имени «Aurin-Custom» с нейтральным промптом по умолчанию — это не ошибка, просто нечего кастомизировать.
Настраивается только тон, роль и формат ответа. Базовые ограничения по опасному и запрещённому контенту сохраняются — как и у остальных моделей Aurin, эта модель их не снимает.

15 Сообщения в Telegram

Отправка произвольного HTML-сообщения пользователю в Telegram напрямую от имени системы — удобно для собственных уведомлений (например, статус заявки в твоём приложении).

POST /api/telegram/send-message
Параметры тела запроса
ПолеТипОписание
telegram_idобязателенstring|numberTelegram ID получателя
textобязателенstringТекст сообщения, поддерживает HTML-разметку Telegram (<b>, <i>, <blockquote>)
javascript
await fetch('https://ferniex-id.vercel.app/api/telegram/send-message', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    telegram_id: '7406372338',
    text: '✅ <b>Твоя заявка одобрена!</b>'
  })
});
typescript
async function sendTelegramMessage(telegramId: string, text: string): Promise<void> {
  await fetch('https://ferniex-id.vercel.app/api/telegram/send-message', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ telegram_id: telegramId, text })
  });
}
sendTelegramMessage('7406372338', '✅ <b>Твоя заявка одобрена!</b>');
python
requests.post(
    "https://ferniex-id.vercel.app/api/telegram/send-message",
    json={
        "telegram_id": "7406372338",
        "text": "✅ <b>Твоя заявка одобрена!</b>",
    },
)

16 Коды ошибок

Почти все эндпоинты возвращают HTTP 200 с полем success: false и текстом ошибки в error — исключение составляет /api/chat, где используются настоящие HTTP-статусы.

ОшибкаГде встречаетсяЧто значит
no_telegrambalance/get, edit/balance, cases/buyУ пользователя не привязан Telegram
not_registeredbalance/getПользователь с таким логином не найден
not_in_botbalance/getАккаунт есть в FernieID, но не найден в базе бота
invalid_modelchat (HTTP 400)Указана несуществующая модель
jailbreak_detectedchat (HTTP 400)Сообщение попало под фильтр защиты от джейлбрейков
— (HTTP 429)chatПревышен суточный лимит токенов
— (HTTP 401)chatНе передан или недействителен API-ключ
Повторные попытки обойти защиту AI-чата фиксируются системой FernieX Defender: после 10 предупреждений аккаунт блокируется в чате навсегда.

17 Лимиты и защита

  • 10 API-ключей максимум на аккаунт (не действует для сайт-администраторов).
  • Коды 2FA и сброса пароля живут 5–10 минут и одноразовые.
  • Пополнение баланса (action: "add") доступно только ключам с типом «Бот».
  • AI-чат имеет суточные лимиты токенов по трём уровням (см. раздел 12) и фильтр джейлбрейков на входящих сообщениях — включая aurin-custom.
  • Каждый запрос через ключ пишется в его лог (IP, время, действие) — историю можно посмотреть в личном кабинете.

18 FAQ

Можно ли использовать ключ на бэкенде и фронтенде одновременно?

Технически да, но операции с балансом (особенно add) лучше держать только на бэкенде — если ключ утечёт из клиентского кода, им сможет воспользоваться кто угодно.

Почему 2FA нельзя отключить для моего приложения?

2FA — часть защиты аккаунта пользователя FernieID, а не настройка конкретного приложения. Если Telegram привязан — код спросят всегда, независимо от того, через какой ключ идёт запрос.

Что будет, если у пользователя нет Telegram?

Вход пройдёт без 2FA, но большинство операций (баланс, уведомления, кейсы, покупки) потребуют привязки — сервер вернёт понятную ошибку no_telegram с готовой ссылкой на привязку.

Можно ли выбрать другую модель для AI-чата?

Да — aurin-lite (быстрая), mistral-medium, aurin (базовая), aurin-pro (с внутренним рассуждением через <thinking>, отдаёт только чистый результат) и aurin-custom (своя персона и системный промпт, см. раздел 13).