API для разработчиков
Публичный REST-API Naminori — стройте свои клиенты повторений, плагины Anki/Obsidian, виджеты, скрипты майнинга. Все данные — только ваши (владельца токена); лукап и озвучка — общие сервисы.
Авторизация
Все запросы требуют персональный токен в заголовке. Токен создаётся после входа в аккаунт — в настройках → «API-токены». Он даёт доступ ТОЛЬКО к данным вашего аккаунта — храните его как пароль, при утечке отзовите в настройках.
Authorization: Bearer nmn_xxxxxxxxxxxxxxxxxxxxxxxx
Альтернативно — заголовок X-Naminori-Token. CORS открыт (авторизация по токену, не по cookie), так что API можно дёргать из браузерных расширений и локальных приложений.
Формат и лимиты
- Ответы — JSON (кроме
/api/v1/audioи/api/v1/dicts/…— бинарные файлы). - Ошибка — это JSON
{ "error": "код", "message"?: "…", "details"?: {…} }: вerrorкороткий машинный код, вmessage— готовая русская фраза для показа человеку (там, где она есть), вdetails— разбор по полям при невалидном теле. - Коды:
400кривой запрос/тело ·401нет/неверный токен ·404не найдено или чужое ·409конфликт (например, удаление непустой колоды) ·413слишком длинный текст/большой файл ·415неподдерживаемый тип файла ·429превышен лимит ·500/502/503сбой на нашей стороне (озвучка, сборка словаря) — запрос можно повторить позже. - Неизвестное значение фильтра — это ошибка, а не «фильтра нет»:
/cards,/queueи/dueотвечают400на чужойtype,state,suspendedили неверный форматdeckId. - Лимиты — на пользователя, окно 1 минута. Чтение (
/me,/due,/queue,/cards,/lookup,/dict) — 240 запросов/мин. Запись:/reviewи/vocab/cards— 240/мин,/vocab/addи/audio— 120/мин,/learn-new, создание и правка колод,/media/store— 60/мин. - Деструктивные операции доступны только в редакторе личных колод (
POST /api/v1/vocab/cardsиDELETE /api/v1/vocab/decks/{id}). Удалить аккаунт, изменить настройки или выполнить админ-операцию через API нельзя.
Профиль и статистика
/api/media/animateСобрать АНИМИРОВАННЫЙ кадр сцены из последовательности картинок (multipart/form-data, поле frames повторяется; 2–24 кадра, до 400 КБ каждый). Возвращает такой же относительный URL, как /api/media/store. Зачем отдельно: браузер анимированный AVIF кодировать не умеет — canvas.toBlob отдаёт один кадр. Зачем вообще: две секунды движения весят около 12 КБ, то есть дешевле статичного скриншота, а сцена по ним узнаётся.
curl -X POST https://naminori.fun/api/media/animate -H "Authorization: Bearer nmn_…" -F "frames=@f001.webp" -F "frames=@f002.webp" -F "frames=@f003.webp"
→ { "ok": true, "url": "/api/media/9f1c….avif", "bytes": 12043 }/api/v1/meПрофиль: имя, целевой уровень, стрик, сколько выучено грамматики и слов.
{ "name": "...", "targetLevel": "N4", "streak": 16, "grammarLearned": 461, "vocabLearned": 2182 }/api/v1/overviewВесь кабинет ОДНИМ запросом: профиль, стрик (текущий + рекорд), сегодняшний прогресс (сделано / осталось по типам), состояние колоды (всего / новые / изучено), активность за 7 дней, время ближайшей карточки и список личных колод. Собрано для Telegram-бота, но подходит любому клиенту, которому нужен «экран дня» без пяти запросов подряд.
{ "name": "…", "targetLevel": "N4", "streak": { "current": 16, "best": 41 },
"today": { "doneVocab": 40, "doneGrammar": 12, "doneTotal": 52,
"remainingVocab": 6, "remainingGrammar": 0, "remainingTotal": 6 },
"deck": { "vocab": { "total": 2182, "new": 90, "learned": 2092 }, "grammar": { … },
"totalCards": 2643, "newCards": 114, "learnedCards": 2529 },
"daily": [ { "date": "2026-07-02", "reviews": 46 } ],
"nextDue": { "vocab": "2026-07-03T09:12:00.000Z", "grammar": null },
"decks": [ { "id": "…", "name": "Аниме", "isDefault": false,
"cardCount": 312, "newCount": 8, "dueCount": 14 } ] }/api/v1/statsДневная статистика занятий — для графиков и heatmap (виджеты, Obsidian, дашборды).
Параметры: days=1..90 (30)
{ "days": 30, "streak": 16, "totals": { "reviewsDone": 812, "newGrammar": 24, "newVocab": 310 },
"daily": [ { "date": "2026-07-02", "reviewsDone": 46, "newGrammar": 2, "newVocab": 25 } ] }/api/v1/stats/todayСводка дня в стиле Anki для экрана «сессия завершена»: сколько карточек, сколько времени, секунд на карточку и фраза под темп. cards — все повторения за день; minutes/secPerCard считаются по записям с замером времени (elapsedMs в POST /api/v1/review).
{ "cards": 52, "timeMs": 684000, "minutes": 11.4, "secPerCard": 13.2,
"phrase": "Ровный темп — так и держите." }Повторения (SRS)
Полный цикл внешнего клиента повторений: очередь → показ карточки → оценка. Расписание — тот же FSRS, что на сайте.
/api/v1/dueСколько карточек ждёт повторения сейчас (грамматика + слова). Необязательный deckId сужает счётчик слов до одной личной колоды.
Параметры: deckId=<uuid>
{ "grammar": 26, "vocab": 46, "total": 72 }/api/v1/queueСодержимое очереди повторений. У type=vocab ДВЕ формы элемента, различайте их по полю kind: "vocab" — слово из словаря (jp/kana/ru + уровень, часть речи, пример, питч и контекст майнинга), "anki" — карточка из импорта .apkg или созданная вручную, у неё front/back/reading вместо jp/kana/ru. Клиент, написанный только под "vocab", покажет на каждой импортированной карточке undefined. type=grammar — cloze-вопросы, форма совсем другая (см. второй пример). У ghost-карточек грамматики cardId=null — они оцениваются только на сайте.
Параметры: type=vocab|grammar (vocab) · limit=1..100 (50) · deckId=<uuid> · context=1 — приложить к каждому слову живое предложение из корпуса (режим «в предложении»: фраза подбирается так, чтобы незнакомых слов в ней было как можно меньше). По запросу, а не всегда: клиенту вроде бота лишний текст в каждой карточке ни к чему. Поле context равно null, если для слова корпус ничего не нашёл — тогда режим просто не предлагают.
{ "type": "vocab", "total": 46, "items": [
{ "cardId": "…", "kind": "vocab", "jp": "勉強", "kana": "べんきょう", "ru": "учёба",
"level": "N5", "partOfSpeech": "сущ.", "exampleJp": "…", "exampleRu": "…",
"accent": 0, "pitch": { "reading": "べんきょう", "type": "平板", "accent": 0 },
"video": { "videoId": "…", "startMs": 61200, "endMs": 63400, "subtitle": "…" },
"textContext": { "kind": "reading", "sourceTitle": "…", "snippet": "…" },
"context": { "jp": "毎日日本語を勉強しています。", "kana": "まいにち…", "ru": "Каждый день учу японский." } },
{ "cardId": "…", "kind": "anki", "front": "猫", "back": "кошка", "reading": "ねこ" } ] }
// type=grammar — другая форма элемента:
{ "type": "grammar", "total": 26, "items": [
{ "cardId": "…", "ghost": false,
"point": { "slug": "te-iru", "title": "ている", "meaningRu": "длительное действие" },
"question": "今、本を___。", "questionKana": "いま、ほんを___。",
"sentenceJp": "今、本を読んでいます。", "sentenceRu": "Сейчас я читаю книгу.",
"answer": "読んでいます", "alternatives": [ "読んでいる" ], "hintRu": "…" } ] }/api/v1/reviewОценить карточку: FSRS-перепланирование + лог + дневная статистика. rating: 1 Again · 2 Hard · 3 Good · 4 Easy. Необязательный elapsedMs (миллисекунды, 0…30 минут) — сколько времени ушло на карточку; без него «сводка дня» у вашего клиента всегда покажет 0 с/карточку. Для грамматики ghost-добивания не создаются (это механика веб-сессии). Ответ содержит undo-токен для отката оценки.
curl -X POST https://naminori.fun/api/v1/review \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"cardId":"<uuid>","rating":3,"elapsedMs":4200}'
→ { "ok": true, "state": "review", "due": "2026-07-06T…", "stability": 4.2,
"undo": { "cardId": "…", "logId": "…", "snapshot": { … } } }/api/v1/review/undoОтменить последнюю оценку («↩ как в Anki»): тело — объект undo из ответа POST /review. Восстанавливает прежнее FSRS-состояние, стирает лог, −1 к дневному счётчику.
curl -X POST https://naminori.fun/api/v1/review/undo \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"cardId":"<uuid>","logId":"<uuid>","snapshot":{…}}'
→ { "ok": true, "cardId": "…" }/api/v1/learn-newДобавить порцию НОВЫХ слов или пунктов грамматики в SRS (аналог «Учить новое» на сайте) с дневными лимитами vocabPerDay/newPerDay. added>0 → перезапросите /queue; added=0 и remaining=0 — лимит дня; added=0 и remaining>0 — новый контент закончился.
Параметры: type=vocab|grammar · count=1..50 (10) · level=N5..N1 · deckId (только vocab)
curl -X POST https://naminori.fun/api/v1/learn-new \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"type":"vocab","count":10}'
→ { "ok": true, "added": 10, "remaining": 15 }/api/v1/cardsМои SRS-карточки с фильтрами и пагинацией. Для vocab/grammar в ответе есть «лицо» (слово/пункт). Неизвестное значение фильтра — 400 с details, а не «молча вся колода»: опечатка в type/state диагностируется сразу.
Параметры: type=vocab|grammar|kana|custom|pitch · state=new|learning|review|relearning · suspended=true|false · deckId=<uuid> · limit=1..200 (50) · offset
{ "cards": [ { "cardId": "…", "type": "vocab", "state": "review", "due": "…",
"suspended": false, "word": { "jp": "猫", "kana": "ねこ", "ru": "кошка" } } ], "limit": 50, "offset": 0 }/api/v1/cardsУправление карточкой: suspend/unsuspend (убрать из очереди, обратимо), bury/unbury (спрятать до следующего учебного дня). Удаления здесь нет сознательно — оно живёт в редакторе личных колод (POST /api/v1/vocab/cards).
curl -X POST https://naminori.fun/api/v1/cards \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"cardId":"<uuid>","action":"suspend"}'Личные колоды
Колоды слов в стиле Anki — те же, что на /vocab/decks. Не путать с /api/v1/decks: там публичные колоды сообщества. Этими ручками пользуются наш десктоп, мобильное приложение и расширение (выбор колоды при майнинге).
/api/v1/vocab/decksМои колоды со счётчиками: всего карточек, новых, к повторению.
{ "decks": [ { "id": "…", "name": "Аниме", "description": null, "isDefault": false,
"cardCount": 312, "newCount": 8, "dueCount": 14 } ] }/api/v1/vocab/decksСоздать колоду. Тело: { name (1..80), description? (до 300) }.
curl -X POST https://naminori.fun/api/v1/vocab/decks \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"name":"Аниме","description":"слова из сабов"}'
→ { "ok": true, "id": "<uuid>" }/api/v1/vocab/decks/{id}Переименовать колоду / сменить описание. Тело: { name?, description? }.
→ { "ok": true } · 404 { "error": "not found", "message": "Колода не найдена" }/api/v1/vocab/decks/{id}Сделать колоду колодой по умолчанию (туда падают слова из майнинга без явного deckId).
/api/v1/vocab/decks/{id}Удалить колоду. Колоду по умолчанию удалить нельзя. Если в колоде есть карточки — 409 { error: "deck not empty", cardCount }; передайте moveToDeckId, чтобы перенести их в другую колоду, либо deleteCards=1, чтобы снести их вместе с колодой (необратимо; в ответе deletedCards). Оба параметра сразу — 400 (судим по наличию moveToDeckId, а не по тому, разобрался ли он: кривой uuid рядом с deleteCards тоже 400, а не молчаливый снос). Кривой moveToDeckId сам по себе — 400.
Параметры: moveToDeckId=<uuid> | deleteCards=1
/api/v1/vocab/decks/{id}/cardsКарточки колоды — это и есть данные редактора колод в десктоп-приложении. Все параметры необязательны: без них приходит весь список, как раньше. q ищет по слову, чтению, переводу и имени исходной колоды Anki; state/pause/type/ankiDeck — те же разрезы, что у фильтров на сайте; perPage включает страницы (page — с единицы, номер за краем зажимается к последней). В ответе: total (всего в колоде), filtered (под фильтром), page, pageCount и facets со счётчиками для каждого разреза — счётчик показывает, сколько останется ПОСЛЕ включения этого фильтра.
Параметры: q=<строка> & state=all|new|learning|review|relearning & pause=all|active|paused & type=all|vocab|anki & ankiDeck=<имя> & page=<n> & perPage=<1..1000>
{ "deck": { "id": "…", "name": "Аниме", "isDefault": false },
"cards": [ { "cardId": "…", "front": "猫", "back": "кошка", "state": "review", "due": "…" } ],
"total": 4512, "filtered": 3, "page": 1, "pageCount": 1, "perPage": 200,
"facets": { "state": { "new": 1, "learning": 0, "review": 2, "relearning": 0 },
"paused": 3, "active": 4509, "vocab": 120, "anki": 4392,
"ankiDecks": [ { "name": "日本語::N5", "count": 4392 } ] } }/api/v1/vocab/decks/{id}/cardsСоздать СВОЮ карточку в колоде (Anki-стиль, itemType=custom) — как кнопка «Добавить карточку» на сайте. Тело: { front (1..500), back (до 2000) } и необязательные reading, partOfSpeech, exampleJp, exampleRu, note. С ними front трактуется как слово, back — как перевод, а лицо и оборот собирает сервер.
→ { "ok": true, "card": { "cardId": "…", "front": "…", "back": "…" } }/api/v1/vocab/decks/{id}/bulkВставить список слов в колоду — как панель «Добавить слова списком» на сайте: каждое слово ищется в словаре и заводится полноценной карточкой. Тело: { words: string[] } (можно и одной строкой — режем по запятым, 、, точке с запятой, переносам и табам). За раз берётся 200 слов, куски длиннее 32 символов пропускаются — и то, и другое возвращается в skipped, а не выбрасывается молча. Различать их обязательно: tooLong — подмножество skipped, которое не пройдёт НИКОГДА (слать повторно бессмысленно), остальное в skipped — хвост сверх порции, его и досылают вторым запросом. elsewhere — слова, чья карточка у вас уже есть, но лежит в ДРУГОЙ колоде: сюда они не переехали, перенести можно через action=move у /api/v1/vocab/cards. Порог частоты ниже обычного: 10 отправок в минуту.
curl -X POST https://naminori.fun/api/v1/vocab/decks/<uuid>/bulk \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"words":["母","父","兄, 姉, 弟"]}'
→ { "ok": true, "added": 4, "exists": 1, "notFound": [], "skipped": [], "tooLong": [], "total": 5,
"elsewhere": [ { "word": "母", "cardId": "<uuid>" } ] }Редактор карточек
/api/v1/vocab/cardsОперации над своими карточками слов: move (перенести в другую колоду), forget (сбросить прогресс, state=new), delete (удалить из повторений), update (личная версия текста), suspend (снять с повторений и вернуть — тоггл над ОДНОЙ карточкой), setSuspended (массовая пауза/возврат: cardIds + suspended, направление задаётся явно), bury (отложить до следующего учебного дня), scene (правка контекста «откуда слово»: предложение, подпись источника, кадр, звук). Пакетные — move, forget, delete и setSuspended, до 1000 id за раз; остальные работают с одной карточкой. Массовую паузу делайте именно setSuspended: одиночный suspend на тысяче карточек упрётся в порог частоты и бросит работу на середине. Единственное место, где API удаляет карточки — сделано под редактор колод; общий /api/v1/cards по-прежнему без деструктивных операций. На чужой или несуществующей карточке операции отвечают ok: false, а не молчаливым успехом.
curl -X POST https://naminori.fun/api/v1/vocab/cards \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"action":"move","cardIds":["<uuid>"],"toDeckId":"<uuid>"}'
→ { "ok": true, "moved": 1 }
# личная версия текста: "" снимает правку и возвращает оригинал словаря,
# отсутствие поля — «не трогать» (как Edit на сайте)
-d '{"action":"update","cardId":"<uuid>","back":"кошка (разг.)","note":"из 3 серии"}'
# снять с повторений (тоггл) и отложить до завтра
-d '{"action":"suspend","cardId":"<uuid>"}' → { "ok": true, "suspended": true }
# массово — направление явное, changed = сколько строк реально изменилось
-d '{"action":"setSuspended","cardIds":["<uuid>"],"suspended":true}'
→ { "ok": true, "changed": 1 }
-d '{"action":"bury","cardId":"<uuid>"}' → { "ok": true, "buriedUntil": "…" }
# «до завтра» двигает buriedUntil, а НЕ due: расписание FSRS не трогается,
# карточка лишь прячется из сегодняшней очереди.
# правка сцены: "" убирает кадр или звук, отсутствие поля — «не трогать».
# image/audio принимают ТОЛЬКО наш путь /api/media/<файл> из POST /api/media/store
-d '{"action":"scene","cardId":"<uuid>","snippet":"…","image":"/api/media/9f1c….webp"}'Словарь и лукап
/api/v1/lookup?q=食べた«Умный» лукап как в расширении: деинфлексия (食べた→食べる), совпадения всех длин секциями (Yomitan-style), питч. Скан идёт слева направо и продолжается после первого слова, поэтому 今学期 отдаёт и 今, и 学期. matchLength — сколько символов запроса покрыло ПЕРВОЕ слово (то, что подсвечивают под курсором), а не весь разобранный кусок. ОБЯЗАТЕЛЬНО смотрите headGroups: столько ПЕРВЫХ элементов groups относятся к слову под курсором, остальные — следующие слова строки; клиент, который этого не знает, покажет чужие статьи как значения того же слова. contextual: true — длина выбрана с оглядкой на продолжение (結んで в 「結んでもらおうか」, а не жадное 結んでも); такой ответ нельзя кэшировать по «форме + одному следующему символу». Пустой q — не ошибка: 200 и hit: null.
Параметры: q=слово (до 64 символов; длиннее — 400 { error: "query too long", maxLength: 64 }) · full=1 — статья ЦЕЛИКОМ (по умолчанию каждое значение режется до 350 знаков с пометкой «показана не полностью»: для попапа по наведению это правильно, для карточки повторения — нет) · ws=слово1,слово2 — пакет вместо q, чтобы вытянуть весь абзац заранее одним запросом (до 80 слов и 5200 символов строки; лишние слова отбрасываются молча, слишком длинная строка — 400 batch too long) · src=jitendex_ru,jmdict,warodai,jitendex — ограничить словари; БЕЗ src отдаются все, настройку пользователя ручка не читает, неизвестные имена отбрасываются
{ "query": "食べた", "hit": { "jp": "食べる", "kana": "たべる", "matchLength": 3,
"headGroups": 1, "ru": "есть; кушать",
"groups": [ { "jp": "食べる", "kana": "たべる", "accent": 2, "senses": [ { "source": "jmdict", "ru": "есть; кушать" } ] } ] } }
пакет на абзац (ключ в hits — ровно та строка, что пришла в ws=; null означает
«слова нет в словаре» — это ответ, его и надо класть в свой кэш):
GET /api/v1/lookup?ws=猫,が,存在しない単語
{ "queries": ["猫","が","存在しない単語"],
"hits": { "猫": { … }, "が": { … }, "存在しない単語": null } }/api/v1/dict?q=ねこПростой поиск по словарю: до 30 статей. Намеренно облегчённый в сравнении с /api/v1/lookup — без деинфлексии и питча, зато ищет и по русскому значению: запрос кириллицей автоматически превращается в поиск по переводу (сначала «значение начинается с этого слова», в конце — случайные вхождения подстроки). Форма ответа НЕ такая, как у лукапа: в каждом hit ровно одна группа, значения не обрезаются, поиск по префиксу запускается только если точное совпадение ничего не дало. Словари всегда все — настройка пользователя здесь не учитывается.
{ "query": "ねこ", "hits": [ { "jp": "猫", "kana": "ねこ", "ru": "кошка" } ] }/api/vocab/addДобавить слово в SRS (майнинг). Опционально text-контекст «откуда слово» — покажется на повторении. Поля text.image / text.audio принимают ТОЛЬКО наш относительный путь вида /api/media/<файл> — его выдаёт POST /api/media/store (см. ниже). Необязательный deckId кладёт слово в конкретную личную колоду. Кроме text есть второй вид контекста — video: сцена YouTube с тайм-кодом реплики, на повторении карточка покажет её.
Параметры: тело: jp · kana · ru · deckId? · text? {kind: reading|manga|grammar|asbplayer, sourceUrl, sourceTitle, snippet, image?, audio?} · video? {videoId, startMs, endMs, subtitle}
curl -X POST https://naminori.fun/api/vocab/add \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"jp":"猫","kana":"ねこ","ru":"кошка","deckId":"<uuid>",
"text":{"kind":"reading","sourceUrl":"https://…","sourceTitle":"…","snippet":"…"}}'
то же, но контекст — сцена YouTube:
-d '{"jp":"猫","kana":"ねこ","ru":"кошка",
"video":{"videoId":"dQw4w9WgXcQ","startMs":61200,"endMs":63400,"subtitle":"猫が好きです。"}}'/api/media/storeЗагрузить кадр сцены или аудио реплики (multipart/form-data, поле file, до 8 МБ; jpg/png/webp/avif, mp3/m4a/aac/ogg/weba/wav — тип проверяется по содержимому). Возвращает относительный URL, который нужно положить в text.image / text.audio у POST /api/vocab/add. Без этого шага прикрепить медиа-контекст к намайненному слову нельзя: внешние ссылки схема не принимает. Звук в форматах webm/ogg/wav перекодируется в mp3 — иначе на iPhone он молча не играет.
curl -X POST https://naminori.fun/api/media/store \
-H "Authorization: Bearer nmn_…" -F "file=@scene.jpg"
→ { "ok": true, "url": "/api/media/9f1c….jpg", "name": "9f1c….jpg" }/api/v1/audio?q=学校Озвучка любого японского текста (MP3, edge-tts, голос Nanami). До 200 символов, 120 запросов/мин.
/api/v1/translateПеревод одной японской реплики на русский силами сервера (Gemini). Нужен клиентам, которые показывают перевод на лету — например, оверлею для визуальных новелл. Почему через нас, а не напрямую: бесключевой веб-эндпоинт Google Translate отвечает антибот-страницей и 429 на каждый запрос (режется адрес, а не HTTP-клиент), у сервера же Gemini ходит через прокси с ретраями. Повтор той же реплики отдаётся из общего кэша на 2000 строк — мгновенно и не тратя квоту.
Параметры: тело: text (japanese, режется до 400 символов). Пустой — 400 empty text, нераспарсенное тело — 400 bad json, отказ Gemini — 502 с причиной в поле error
curl -X POST https://naminori.fun/api/v1/translate \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"text":"また明日ね"}'
→ { "ok": true, "text": "До завтра!" } # повтор: + "cached": true/api/v1/grammar/lookup?q=てもいい«Что это за конструкция»: по куску японского находит грамматические пункты Naminori. Нужен клиенту, который разбирает предложение и хочет показать не только слова, но и грамматику. Поле total — это число ВОЗВРАЩЁННЫХ пунктов (после limit), а не сколько всего совпало.
Параметры: q — до 80 символов (в ответе возвращается уже обрезанным) · limit
/api/v1/dicts/naminori-grammarСловарь грамматики Naminori (конструкции N5–N1 с размеченными поверхностями) в формате Yomitan v3 zip — тем же способом, что и словарь слов. Параметр v НЕ выбирает версию: собирается и отдаётся всегда текущий архив, а v влияет только на заголовки кэширования (совпал с текущей ревизией — immutable, иначе no-cache).
Параметры: v — ревизия для кэш-заголовков (необязательно)
/api/v1/dicts/naminori-ru/meta«Паспорт» словарного пакета: ревизия и РАСКЛАД ПО ИСТОЧНИКАМ (сколько статей из каждого словаря). По нему клиент понимает, что скачанный когда-то пакет устарел. Судить надо именно по раскладу, а не по числу статей: 23.08.2026 с сервера пропал основной русский словарь, пакет пересобрался без него, и число термов осталось прежним — подмену по количеству заметить было нельзя. Публично, без токена.
/api/media/{файл}Отдаёт намайненный кадр или звук реплики, ранее загруженный через POST /api/media/store. Публично (адрес — случайный UUID). ВНИМАНИЕ: у этой ручки, в отличие от всех /api/v1/*, НЕТ CORS — кросс-доменный fetch из расширения или веб-клиента упрётся в политику; в <img> и <audio> файл работает как обычно. Поддерживает Range: без этого <audio> в iOS Safari не проигрывает файл.
/api/v1/dicts/naminori-ruГотовый словарь Naminori (Yomitan v3 zip, питч Kanjium и частотность форм) — для импорта в Yomitan-совместимые клиенты и наше расширение. Основной режим — пакет на один словарь: src=jitendex_ru (Колобок + грамматика Naminori), src=jmdict, src=jitendex; у таких пакетов постоянные названия. Без src — полный пакет, src=a,b — комбинированный (оба для старых клиентов); грамматика Naminori только там, где есть Колобок. Готовый пакет держится в кэше сутки; если его пришлось собирать заново — не чаще трёх сборок за 10 минут с адреса (иначе 429), а одновременные сборки встают в очередь (503, если очередь занята).
Контент
/api/v1/grammarКаталог опубликованной грамматики N5–N1 в методическом порядке. total — сколько пунктов подошло ВСЕГО (не размер страницы), так что каталог можно листать по offset.
Параметры: level=N5..N1 · q=поиск (название/кана/ромадзи/перевод) · limit=1..600 (100) · offset=0…
{ "total": 552, "limit": 100, "offset": 0, "points": [ { "slug": "temo-ii", "level": "N5", "title": "〜てもいい", "meaningRu": "можно; разрешено" } ] }/api/v1/grammar/temo-iiПолный пункт: объяснение (markdown), нюансы, структура, похожие конструкции + до 30 примеров. Разметка фуриганы `{漢|かな}`, целевая конструкция в `<g>…</g>`.
{ "point": { "slug": "temo-ii", "title": "〜てもいい", "explanationMd": "…", "structure": […] },
"examples": [ { "jp": "{食|た}べ<g>てもいい</g>ですか。", "ru": "Можно поесть?", "answer": "てもいい" } ] }/api/v1/grammar/lookup?q=食べてもいいですか«Что это за конструкция»: запрос нормализуется (тильда-заполнитель, многоточие, скобочные пояснения, полноширинные пробелы) и результаты ранжируются — точное совпадение, начало, вхождение в любую сторону. Главное отличие от каталожного ?q= — вхождение «наоборот»: живая фраза 食べてもいいですか находит пункт 〜てもいい, чего ILIKE по названию не сделает никогда.
Параметры: q=конструкция (до 80 символов) · limit=1..10 (5)
/api/v1/grammar/checkПроверить ответ на грамматическую клозу ТОЧНО как на сайте: сначала ромадзи → кана (как поле ввода в тренажёре), затем общая проверка (кандзи↔кана, эквивалентные формы, вежливость). Позволяет принимать ответ, набранный латиницей, и не переписывать выверенную логику приёма у себя. Тело: { input, answer, alternatives?, pointSlug? } — answer и alternatives приходят из GET /api/v1/queue?type=grammar.
curl -X POST https://naminori.fun/api/v1/grammar/check \
-H "Authorization: Bearer nmn_…" -H "Content-Type: application/json" \
-d '{"input":"temoii","answer":"てもいい","pointSlug":"temo-ii"}'/api/v1/decksПубличные колоды сообщества (по популярности, до 100). В каждой строке — id, title, description, level, saves (сколько раз сохранили), items (карточек) и url — адрес колоды на сайте. Дальнейшая работа с колодой (посмотреть состав, сохранить себе) возможна только на сайте: ручек /decks/{id} в API нет.
{ "decks": [ { "id": "…", "title": "Кухня и еда", "level": "N4",
"saves": 128, "items": 300, "url": "/decks/shared/…" } ] }Служебное
/api/v1/extension/versionВерсия расширения в готовом zip и ссылка на скачивание — установленная копия сравнивает её со своей и зовёт обновиться. Единственная ручка без токена. 503, если zip ещё не собран.
{ "version": "3.2.10", "url": "https://naminori.fun/apps/naminori-extension.zip" }Пример: мини-клиент повторений на JS
const API = "https://naminori.fun";
const H = { Authorization: "Bearer nmn_…", "Content-Type": "application/json" };
const { items } = await fetch(`${API}/api/v1/queue?type=vocab&limit=10`, { headers: H })
.then((r) => r.json());
for (const card of items) {
console.log(card.jp, "→ ваш ответ…", card.ru);
await fetch(`${API}/api/v1/review`, {
method: "POST", headers: H,
body: JSON.stringify({ cardId: card.cardId, rating: 3 }),
});
}Базовый URL: https://naminori.fun. По вопросам интеграций пишите в Telegram @s1n3off или на hello@naminori.fun. API стабилен в пределах v1; о breaking changes предупредим здесь.