AstroWay/api v2.325.2 · changelog
усі системи в нормі

// product · release notes

Changelog що нового

Лише зміни, що впливають на код інтегратора: нові ендпоінти, оновлення SDK, зміни в /openapi.json, правки цін у кредитах, deprecation, breaking changes. Найновіші згори. Внутрішні правки сайту сюди не потрапляють.

Latest · v2.325.2 Endpoints · 865 CI tests · 888 API contract · v1 stable · 12-mo deprecation

2026-09-14 · Прострочений план ще три дні відповідає, а не повертає 402

Коли оплачений період закінчується, запити тепер ще 3 дні обслуговуються за вашим тарифом. Раніше 402 SUBSCRIPTION_EXPIRED приходив у ту ж секунду, коли спливав expires_at, і інтеграція зупинялась, щойно оплата запізнилась.

Що робити з вашим кодом. Нічого обов’язкового. Якщо хочете дізнатися про прострочення раніше за своїх користувачів, логуйте X-Subscription-Status: grace і X-Subscription-Grace-Ends: вони приходять тільки в ці три дні. Після них відповідь, як і раніше, 402 SUBSCRIPTION_EXPIRED, а в details тепер є expired_at і grace_ended_at.

Те саме стосується add-on паків.

2026-09-13 · houseSystem відхиляє код, якого не існує

Раніше поле приймало будь-який рядок. Рушій читає з нього тільки першу літеру, тож невідома літера мовчки давала Placidus, а у відповіді houses.system повторював те, що ви надіслали. Тобто карта, порахована за Placidus, поверталася підписана системою, якої в розрахунку не було.

Тепер приймаються рівно ті 25 кодів, які вміє рушій: P K R C E W B M O A T V D F G H I i L N Q S U X Y. Регістр значущий, I та i це різні системи. Усе інше отримує 400 з переліком у тексті помилки.

Що робити з вашим кодом. Нічого, якщо ви надсилаєте код літерою або не надсилаєте поле зовсім: типове значення лишилось P. Якщо ви надсилали назву ("Placidus", "Koch"), запит почне повертати 400: раніше він працював випадково, бо збігалася перша літера, і так само випадково "Zodiac" давав Placidus. Замініть назву на код.

Перевірено на проді до зміни: houseSystem: "ZZZ" повертав куспід 2 на 192.865, байт у байт як "P", і при цьому system: "ZZZ".

2026-09-13 · allowlist більше не значиться в специфікації

origin_restriction.type: "allowlist" прибрано з openapi.json: у схемах CreateKey і UpdateKey лишилось два значення, server-only і public.

Що робити з вашим кодом. Нічого. Значення приймається й далі та означає те саме, що public, тож наявні виклики працюють без змін. Якщо ви генеруєте клієнт зі специфікації, після оновлення в ньому просто не буде третього варіанта, який дублював другий.

Це той анонсований крок, про який ішлося 12 вересня, коли обидва написання почали створювати однаковий ключ класу pk_. Прибрано саме з документа, а не з приймання: відмовляти у значенні, яке специфікація перелічувала ще вчора, гірше, ніж чесно сказати, що воно застаріле.

2026-09-13 · Ключ можна обмежити списком ендпоінтів

POST /v1/keys і PATCH /v1/keys/{id} приймають allowed_endpoints: список шляхів під /v1, точних (chart) або просторів імен (embed/*). Ключ зі списком викликає тільки те, що в списку, незалежно від тарифу.

Що робити з вашим кодом. Нічого, поки не задасте список: без нього поведінка не змінилась, і зараз списку немає в жодного ключа. Якщо задаєте, обробіть 403 ENDPOINT_NOT_IN_SCOPE, у details приходить сам список. null знімає обмеження, порожній список не приймається, бо читався б і як “усе”, і як “нічого”.

Обмеження діє на обидва класи ключів і на /v1/public/* та /v1/embed/* теж. Службові читання (/v1/auth/keys/me, /v1/keys/usage) з-під нього не звільнені, на відміну від кредитів: область це право, яке ви виписали ключу, а не тарифікація, і ключ у вихідному коді сторінки не повинен віддавати витрати акаунта.

2026-09-13 · Ключ для браузера став окремим класом pk_

POST /v1/keys з origin_restriction.type: "public" тепер видає ключ із префіксом pk_live_ або pk_test_ і вимагає непорожній allowed_origins: без списку приходить 400. Для цього класу правило походження зворотне до звичайного. Запит без Origin і без Referer отримує 403 ORIGIN_REQUIRED, а не проходить як серверний, і Origin: null з <iframe sandbox> рахується відсутнім походженням, а не дозволом.

Що робити з вашим кодом. Нічого, якщо ви користуєтесь звичайними ключами: aw_live_ і aw_test_ поводяться як раніше, режими server-only та allowlist не змінились, і жоден наявний ключ не переведено. Якщо ви розбираєте префікс ключа на своєму боці, додайте pk_. Якщо створюєте ключі через API з type: "public", додайте до запиту список доменів.

Раніше цей режим означав “будь-який origin”, тобто ключ, призначений лежати у вихідному коді сторінки, працював і з curl без жодного заголовка. Ключі, створені до 13 вересня, працюють як працювали; змінюється тільки те, що видається далі.

Заразом ключ, надісланий на /v1/public/* або /v1/embed/*, тепер справді перевіряється. Ці два префікси змонтовані до перевірки авторизації, тому політика походження на них не виконувалась зовсім: той самий server-only ключ отримував 403 на /v1/keys/usage і 200 на /v1/embed/moon-phase з тим самим чужим Origin. Запит без ключа на цих префіксах не змінився: він і далі анонімний, з лімітом за IP, бо саме так до нас приходить кожен віджет в <iframe>.

type: "allowlist" тепер видає той самий клас pk_, що й type: "public". Раніше цей режим створював секретний ключ, який приймався з перелічених доменів, але так само проходив і без походження взагалі, бо це звичайний серверний пропуск. Тобто саме той режим, який документація радила для віджетів, лишався слабшим. Значення нікуди не зникло і далі приймається, щоб не ламати згенерованих клієнтів: воно просто створює те саме, що public, і відповідь це показує - префікс ключа pk_ і origin_restriction.type: "public". Наявні ключі зі списком не переведені й працюють як працювали.

2026-09-13 · Ліміт запитів ділиться між дозволеними походженнями

Якщо у ключа є список дозволених походжень, кожен запис списку тепер отримує власну частку ліміту запитів: ліміт ключа поділений на кількість записів, округлено вгору. Раніше будь-яке одне походження могло вибрати весь ліміт ключа.

Що робити з вашим кодом. Нічого, якщо в списку один запис: частка дорівнює лімітові й поведінка не змінилась. Якщо записів кілька, врахуйте, що один ваш сайт більше не займе квоту решти. Додався 429 з поясненням, який саме запис вичерпано, і два заголовки, X-RateLimit-Origin-Limit та X-RateLimit-Origin-Remaining; вони приходять лише на запитах, пропущених за записом зі списку.

Лічильник ведеться за записом списку, а не за хостом, який прийшов: *.example.com це один лічильник на всі піддомени. Інакше обмеження обходилось би вигадуванням нового піддомена на кожен запит.

2026-09-13 · Модель сумісності перерахована, числа змінились

Бал сумісності рахується за новою моделлю. Поля ті самі (score, label, harmony, tension), типи ті самі, числа інші в кожній парі. Додалось поле top: п’ять найвагоміших аспектів, кожен зі своїм strength і знаком sign.

Зачеплені /v1/synastry, /v1/match/score, /v1/group-synastry, /v1/public/synastry і віджети на /v1/embed/*.

Що робити з вашим кодом. Якщо ви зберігали бал у себе, перерахуйте: та сама пара тепер дає інше число. Якщо ви порівнюєте бали між парами або сортуєте за ними, нічого робити не треба, шкала лишилась 0-100. Порогів ми не змінювали: challenging до 40, mixed до 52, balanced до 64, вище harmonious.

Чому. Попередня формула була harmony / (harmony + tension), і на краях вона ламалась: один трин давав рівно 100, один квадрат рівно 0, а з’єднання Сонця з Сатурном рахувалось гармонією. Нова модель зважує аспект і пару тіл, враховує мінорні аспекти, читає з’єднання по тілу, а не по парі, і нормує результат так, щоб середина шкали була серединою.

Разом з цим /v1/match/score перестав суперечити сам собі: його greenFlags і redFlags рахувались за старим правилом з’єднання, тож один і той самий аспект міг бути “зеленим прапорцем” і водночас входити в tension. Тепер прапорці й бал говорять одне й те саме.

Там же змінився порядок topAspects. Він рахувався окремою формулою ваги, тож “головні” аспекти не збігались з тими, з яких склався бал. Тепер це та сама вага, що й у top, і той самий порядок. Якщо ви берете topAspects[0] як головний аспект пари, це тепер інший аспект.

2026-09-12 · Бюджет кредитів на окремий ключ

Кредити спільні на весь акаунт, тож один ключ міг витратити їх усі. PATCH /v1/keys/{id} тепер приймає credits_cap_cycle: стеля саме для цього ключа, у кредитах за цикл.

Що робити з вашим кодом. Нічого, якщо бюджет не задавати: без нього поведінка не змінилась. Якщо задаєте, обробіть новий 429 KEY_BUDGET_EXHAUSTED. Він приходить, коли ключ вичерпав свій бюджет, навіть якщо на акаунті кредити ще лишились, і навіть на тарифі з overage: бюджет, який можна перевитратити за гроші, це не бюджет.

null знімає обмеження, 0 означає ключ, яким не можна витратити нічого. Лічильник той самий, що й у кредитів, і обнуляється разом із циклом тарифу. Поки бюджет заданий, кожна відповідь несе X-Key-Budget-Limit і X-Key-Budget-Remaining.

2026-09-12 · Список доменів браузерного ключа тепер редагується

PATCH /v1/keys/{id} приймав тільки name. Тепер він приймає й origin_restriction, тож домен до живого pk_ ключа додається без створення другого ключа.

Що робити з вашим кодом. Нічого, якщо список ваших доменів не змінюється. Якщо ви створювали новий ключ щоразу, коли треба було додати домен, тепер досить одного PATCH: старий ключ не доводиться лишати живим “про всяк випадок”.

Новий список діє з наступного запиту, id і сам рядок ключа лишаються ті самі, після rotate список зберігається. Клас ключа не редагується: pk_ не стає server-only, секретному aw_ список не додається, порожній список теж не приймається. Усі три випадки це 400 з поясненням, а не мовчазний перезапис.

Окремо: відповідь на невідомий ключ тепер називає обидва класи, aw_ для сервера і pk_ для браузера, замість самого лише “Invalid API key”.

2026-09-11 · BaZi: увесь класичний апарат, істинний сонячний час і одна депрекація

Вісім нових шляхів. POST /v1/bazi/chart віддає карту цілком; поруч односкладові hidden-stems (藏干), na-yin, life-stages (十二長生), symbolic-stars (神煞), interactions (刑冲合害) і strength. POST /v1/chinese/true-solar-time виправляє годинник під довготу місця народження.

Там, де традиції розходяться, віддаються обидва читання, а не одне: напрям іньських стовбурів у дванадцяти стадіях, обидві позиції іньського леза, вельможа для 庚, стихія з’єднання 午未. Пороги сили названі нашою конвенцією, а не каноном.

Депрекація. elementCounts, dominantElement і missingElements у відповіді /v1/bazi/element-balance рахують тільки рік і місяць, чотири ієрогліфи з восьми, і не відкривають гілки. Вони відповідають без змін до 2027-09-08 і несуть заголовки Deprecation і Sunset. Повний рахунок живе в новому об’єкті balance.

Що робити з вашим кодом. Якщо ви читаєте elementCounts, перейдіть на balance.visible (ті самі вісім ієрогліфів як цілі числа) або balance.weighted (з розподілом прихованих стовбурів). Якщо ні, нічого: усе інше додано, нічого не змінено. Опція trueSolarTime вимагає longitude, time і timezoneOffset разом, інакше 400.

2026-09-11 · Біоритми тепер віддаються даними, а не тільки картинкою

POST /v1/render/biorhythm малював три кривих, але прочитати числа з SVG було неможливо. З’явився POST /v1/wellness/biorhythm: ті самі цикли у вигляді JSON.

Що робити з вашим кодом. Нічого, якщо вам достатньо картинки: /render/biorhythm не змінився і малює те саме. Якщо ви розбирали SVG або рахували цикли самостійно, тепер можна брати готові значення.

На кожен день у відповіді приходить значення трьох циклів у діапазоні від -1 до 1 (помножте на 100, якщо потрібні відсотки) і номер дня життя. Окремо приходить список критичних днів: це дні, коли цикл перетинає нуль, а не дні з малим значенням. День народження критичний для всіх трьох циклів одразу, бо всі вони починаються з нуля.

birthDate обов’язковий, rangeStart і rangeEnd ні: без них береться сьогодні плюс 29 днів. Діапазон включає обидві межі, максимум 366 днів, перевернутий діапазон дає 400 замість порожньої відповіді.

Метод не має доказової бази: це нумерологічна конструкція кінця XIX століття. Відповідь несе про це окреме попередження, і ми не радимо будувати на ній нічого, крім розваги.

2026-09-10 · Сім опублікованих схем відповіді описували поля, яких API не віддає

Схема відповіді в /openapi.json будується з прикладу, який ми пишемо для кожного ендпоінта. Дванадцять таких прикладів описували поля, яких API не віддає. Тепер це захоплення живої відповіді.

ЕндпоінтБуло в схеміНасправді
POST /v1/keysapiKey, rateLimitapi_key, api_key_masked, rate_limit, credits_limit, credits_used, origin_restriction
GET /v1/keys/usagetopEndpoints[].totaltopEndpoints[].requests і .credits, плюс блок credits
GET /v1/statusuptime, ephemerisLoaded, wasmReady, epheFiles[]key, usage, limits, endpoints, daily; без ключа 400 MISSING_KEY, а схема казала, що ключ не потрібен
GET /v1/healthstatus, timestampплюс version і uptime_seconds, які й раніше приходили
POST /v1/interpret/natalsummary, sections{}interpretation, disclaimer, model, language, chart_summary
POST /v1/interpret/synastrysummary, dynamics, outlookinterpretation, disclaimer, model, language
POST /v1/interpret/elementelementinterpretation, element_balance
POST /v1/interpret/placementplacementinterpretation, requested_planet
POST /v1/synastryaspects[] з body1, chart1PlanetcrossAspects[] з planet1, planet2, isApplying, плюс chart1, chart2, compatibility
POST /v1/horoscope/dailytitle, body, transitFacts[]horoscope, disclaimer, model, language
POST /v1/planetsplanets[].speedspeedLong, speedLat, speedDist, плюс name, isRetrograde, declination
80 шляхів /v1/numerology/*lifepath, soulurgelifePath, soulUrge: дефіс прибирали замість camelCase

Самі відповіді не змінилися: помилка була в описі, а не в API.

Того ж роду було ще шістнадцять операцій, чия схема відповіді не називала жодного поля: чотирнадцять каталогів Таро, чиї приклади читалися як [...78 cards...] і не парсились узагалі, плюс /health і /version, які несли приклад без схеми. Тепер усі приклади це захоплення живих відповідей. Останні дві, POST /v1/mcp/streaming і POST /v1/mcp/tool-call-stream, порожніми були не помилково: вони віддають text/event-stream, а не JSON, і тепер спека так і каже, з описом форми кожного кадру. Генератор більше не пообіцяє вам розбірний JSON там, де приходить потік.

Що зробити з вашим кодом. Якщо ви генерували типи з /openapi.json для цих ендпоінтів, перегенеруйте: поля, які ви типізували, у відповіді ніколи не приходили, і код, що на них покладався, отримував undefined. Найчастіший випадок: ключ, створений через POST /v1/keys, читається як api_key.

2026-09-09 · Ліліт малюється на всіх західних зображеннях, і з’явився bodies

Точка Ліліт (середній апогей Місяця) поверталася у відповіді /v1/chart під іменем mean Apogee, але не потрапляла на жодне зображення: гліф і зіставлення імен існували, а списки тіл у рендерерах її не несли. Тепер вона малюється гліфом ⚸ на wheel-western, bi-wheel, tri-wheel, composite, cosmogram і aspect-grid. На трьох ведичних форматах її свідомо немає: чорний Місяць не належить джйотішу.

Постер /v1/reports/stellaforge друкував сирий підпис ефемерид, ♆ ☌ mean Apogee, у списку аспектів. Тепер там гліф.

Що робити з вашим кодом. Якщо ви хочете попередній набір тіл, попросіть його явно: options.bodies приймає список і звужує типовий набір рендерера. Попередній вигляд це ["Sun","Moon","Mercury","Venus","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto","North Node","Chiron"]. Якщо поля немає, набір типовий, тож якщо ви нічого не міняєте, у вас нічого не ламається. Порожній список повертає 400, а не типовий набір: порожнім він майже завжди виходить випадково, і мовчазне повернення до типового приховало б це. Ім’я поза списком тіл, які ми вміємо малювати, теж повертає 400, а не зникає тихо.

2026-09-04 · Публічні ендпоінти: окрема квота для сайтів, які рендерять на сервері

Публічний доступ без ключа рахувався тільки за IP: 30 запитів на годину. Для браузера це правильно, бо кожен відвідувач приносить власний IP і власну квоту. Для сайту, який робить запити зі свого сервера, це не працює: одна машина ходить за всіх відвідувачів і вичерпує 30 запитів за кілька перезавантажень сторінки.

Тепер, якщо запит несе заголовок X-AstroWay-Site-URL з адресою сайту, діє інша квота: 300 запитів на годину на сайт і стеля 600 запитів на годину на IP. Без заголовка все як раніше, 30 на годину на IP.

Що робити з вашим кодом. Якщо ви ходите з браузера або вставляєте iframe-віджети /v1/embed/*, не робіть нічого: поведінка не змінилася, і квота, як і раніше, у кожного відвідувача своя. Якщо запити робить ваш сервер, додайте X-AstroWay-Site-URL: https://ваш-домен/ і кешуйте відповіді на своєму боці. Заголовок уже був дозволений у CORS, окремих налаштувань не потрібно.

Заголовок це декларація, а не облікові дані, тому стеля на IP залишається: скільки б доменів не назвали з однієї адреси, сумарно звідти піде не більше за 600 запитів на годину. Обидва лічильники потрібні одночасно: лічильник сайту спрацьовує, коли один сайт ходить з багатьох адрес (CDN, балансувальник), лічильник IP спрацьовує, коли з однієї адреси приходить багато імен.

У відповіді з’явився заголовок X-RateLimit-Scope зі значенням ip або site. Він каже, який лічильник зараз найтісніший, і решта заголовків X-RateLimit-* описують саме його.

2026-09-03 · Публічна синастрія без ключа, і планетарні години більше не брешуть про поточну годину

POST /v1/public/synastry віддає оцінку сумісності й сітку кросаспектів для двох карт народження без ключа. Відповідь навмисно маленька:

{ "score": 68, "label": "harmonious",
"aspects": [{ "planetA": "Pluto", "planetB": "Saturn", "aspect": "Square", "orb": 0.0509, "applying": false }],
"count": 45 }

Аспекти відсортовані за орбом, найточніші перші, тож перші шість це ті самі шість, які показує віджет. За ключем лишається все інше, що віддає автентифікований POST /v1/synastry: тлумачення кожного аспекту, накладення по домах, композит і Davison, і самі дві натальні карти.

Виклик коштує 3 одиниці публічної корзини, а не 1. Це дві карти й матриця на 45 клітинок, тому рахувати його як фазу Місяця було б неправильно. Ліміти ті самі: 10 таких викликів на годину з незаявленої адреси, 100 на заявлений сайт, 200 на адресу. Відповідь несе заголовок X-RateLimit-Cost: 3.

POST /v1/planetary-hours: поле isCurrent більше не приходить порожньою обіцянкою. Воно публікувалося на всіх 24 годинах і на всіх 24 було false, бо роут ніколи не передавав рушію поточну годину. Тепер її передає той, хто питає: надішліть atLocalHour (десяткові години місцевого часу, наприклад 15.2), і година, що містить цей момент, отримає isCurrent: true. Без цього поля немає взагалі: відповідь кешується, і прапорець з годинника сервера все одно був би застарілим на момент рендеру.

Полярний день і полярна ніч більше не повертають 24 порожні години. Раніше відповідь казала, що планетарні години не визначені, і все одно віддавала таблицю, де всі проміжки нульові. Тепер hours порожній, а sunTimes.warning пояснює чому. Значення sunTimes.polarState описані в схемі: normal, polar-day, polar-night.

Що зробити з вашим кодом. Якщо ви читали isCurrent, він у вас завжди був false: додайте atLocalHour до запиту або рахуйте поточну годину самі з startHour і endHour. Якщо ви малюєте таблицю годин, перевірте порожній масив для полярних широт.

2026-08-31 · Аянамса тепер приймається за назвою, і десять сторінок документації виправлено

Ведичні ендпоінти завжди читали числовий ayanamsaId. Документація при цьому показувала "ayanamsa": "lahiri" на десятку сторінок, а схема була passthrough, тому рядок проходив валідацію і мовчки ігнорувався: відповідь рахувалась по Lahiri, що б ви не надіслали. Перевірено на /v1/vedic/dashas/vimshottari/maha: "lahiri" і "raman" давали ту саму сидеричну довготу Місяця, тоді як ayanamsaId 1 і 3 дають різні накшатри.

Тепер назва працює. Приймаються fagan-bradley, lahiri, de-luce, raman, krishnamurti (він же kp), yukteshwar, jn-bhasin, true-citra, djwhal-khul. Явний ayanamsaId має пріоритет, якщо надіслані обидва. Невідома назва тепер повертає 400 INVALID_FIELD зі списком допустимих замість того, щоб тихо відпрацювати по Lahiri. Обидва поля описані в /v1/openapi.json, тож потраплять у згенеровані клієнти.

Що зробити з кодом. Якщо ви надсилали ayanamsa з назвою, відмінною від lahiri, ваші відповіді досі рахувались по Lahiri, а тепер рахуватимуться по обраній школі: числа зміняться. Якщо надсилали назву поза списком вище, запит почне повертати 400.

Окремо виправлено десять сторінок документації, приклади на яких не працювали: POST /v1/keys приймає name, а не label; /v1/horoscope/daily це sign + date + language, а не натальна карта; /v1/transit-calendar чекає дані народження всередині natal; /v1/vedic/muhurat/* приймає search_window_start / search_window_end / topN. Ендпоінтів /v1/interpret/aspects і /v1/vedic/vargas не існує: замість другого є окремий шлях на кожну варгу, наприклад /v1/vedic/varga/D9.

Ще дві сторінки радили передавати zodiac=tropical заради тропічного Vedic. Такого поля немає, а ведичні ендпоінти в будь-якому разі рахують сайдерично і zodiacType у тілі ігнорують. Якщо ви це надсилали, воно нічого не робило; тропічні позиції дає західна гілка (/v1/chart).

2026-08-28 · Параметри запиту, які раніше не перевірялися, тепер описані й валідуються

Шість ендпоінтів читали поле, якого не було в їхній схемі. Таке поле не перевірялося й не потрапляло до /openapi.json, тож ішло в розрахунок як є і ламало відповідь трьома різними способами, і завжди під статусом 200.

Що робити з вашим кодом. Якщо ви надсилали коректні значення, не робіть нічого: поведінка не змінилася, числові рядки так само приводяться до чисел. Якщо ви надсилали щось некоректне, тепер прийде 400 з INVALID_INPUT замість тихо зіпсованої відповіді. Нові параметри тепер описані у /openapi.json, тож вони з’являться у SDK і MCP після наступної регенерації.

orb у /v1/parallel-aspects і /v1/midpoint-trees був найнебезпечнішим: нечислове значення робило хибним кожне порівняння, і замість шести аспектів чи десяти дерев поверталася порожня відповідь. Порожній масив читається як “нічого не знайдено”, а не як “поганий запит”, тож помітити це на своєму боці було неможливо.

targetDate у /v1/vedic/jaimini/dasha-summary і /v1/vedic/doshas/kp/sade-sati повертав усі даші як null. key і maxAge у /v1/primary-directions тихо псували вибірку: key тут це швидкість у градусах на рік, а не назва режиму, і тепер це число з межами.

Друга хвиля закрила клас повністю. parts у /v1/arabic-parts і maxYears у /v1/firdaria так само віддавали порожній результат замість 20 жеребів і 63 періодів. termsData і decansData у /v1/almuten, /v1/essential-dignities, /v1/hyleg і /v1/receptions роняли обробник у 500. /v1/hd/incarnation-cross узагалі не перевіряв номери воріт, а /v1/human-design/transits брав натальну дату, якої не було в схемі, і накладав транзити на хибну карту, і відповідь при цьому виглядала звичайною.

2026-08-27 · Специфікація тепер описує кожне тіло запиту й відповіді публічних роутів

/v1/openapi.json віддавав для 246 шляхів порожнє тіло запиту: {"type": "object"} без жодного поля. Ендпоінти працювали й перевіряли вхід як завжди, але специфікація про цей вхід не казала нічого, тому згенерований із неї клієнт не мав типу для запиту, а агент, який читає схему, не бачив назв полів. Тепер таких шляхів нуль.

Найбільші групи: нумерологія (50 шляхів), таро (47), китайський блок (14), тварини (14), варги (16), дзи-вей (13), бізнес (11), базі (11), фінанси (10), велнес (9), майя (8). Двадцять шість шляхів, чиї обробники не читають тіло взагалі (16 фігур геомантії, 5 ліній хіромантії, чотири стріми, каталог рун), тепер посилаються на EmptyBody: раніше специфікація казала “тіло може бути будь-яким”, тепер каже правду - його немає.

PATCH і PUT не мали тіла в специфікації взагалі. Генератор описував його тільки для POST, тому PATCH /v1/whitelabel/config не публікував нічого, хоча роут перевіряє кожен виклик. Тепер у нього є схема.

Вісім публічних роутів не мали схеми відповіді. По всій специфікації її мали 733 операції з 758, а без неї лишалися саме ті, які читають server-side без ключа: POST /v1/public/chart, POST /v1/public/human-design, три гороскопи, GET /v1/public/tarot/daily, /moon-phase і /planet-of-day. Тепер кожна віддає ok, data і _footer з полями всередині; обидва POST беруть схему в свого автентифікованого двійника, тож поле, додане в /v1/chart, не може зникнути з /v1/public/chart.

Заразом виправлені надруковані приклади, які були неправильні: у публічному таро position і card показувалися рядками, хоча це об’єкти, а /public/moon-phase і /public/planet-of-day не показували localized взагалі. Приклади /v1/chart і /v1/human-design не містили семи й шести полів верхнього рівня відповідно, а channels був надрукований у формі, якої рушій ніколи не повертав.

Що зробити з вашим кодом. Нічого, якщо ви пишете запити руками: жодне поле не перейменоване, не додане й не прибране, валідація та сама. Якщо ви генеруєте клієнт зі специфікації, перегенеруйте його: там, де раніше було unknown, тепер тип із полями, і публічні відповіді теж типізовані.

2026-08-24 · Документація ведичної сумісності виправлена, тіло запиту це chart1/chart2

Сторінка /vedic/compatibility/ показувала тіло запиту з полями person1 і person2. Жоден із шести ендпоінтів ніколи їх не приймав: схема весь час вимагала chart1 і chart2, тому кожен приклад із тієї сторінки повертав 400 INVALID_INPUT. Сама схема не змінювалась, змінилась документація.

Що ще було неточним на тій сторінці і тепер відповідає реальності:

  • /vedic/compatibility/full повертає data.manglik.partner1, partner2 і verdict. Ключів person1.present, cancelled і bhrigu там немає, Bhrigu-Bindu це окремий ендпоінт /bhrigu-match.
  • Відповідь /ashtakoot має вигляд data.{method, school, boy, girl, total, max, threshold, points{}, doshas[], recommendation}. Полів passing_threshold, verdict, breakdown[] і warnings[] не існує.
  • Вартість: mangal-match, bhrigu-match і full це 50 кредитів кожен, а не 20, 30 і 100. Ціни не змінювались, у таблиці були неправильні числа.

Приклади на сторінці тепер запущені проти продакшна, а відповідь у JSON це дослівний захват для тієї самої пари, що в запиті вище.

2026-08-22 · Повтор запиту з кешу більше не списує кредити

Ми пишемо на сторінці вартості і в конвенціях, що відповідь із заголовком X-Cache: HIT не коштує кредитів. Насправді вона коштувала повну ціну: списання відбувається до кешу, і нічого його не скасовувало. Перевірено трьома однаковими викликами /v1/acg/by-category: два з них повернули X-Cache: HIT, і баланс щоразу зменшувався на 50.

Тепер повтор ідентичного запиту протягом 5 хвилин коштує 0, а X-Credits-Used і X-Credits-Remaining у відповіді показують це чесно.

Той самий запит іншою мовою міг повернути чужу мову. Ключ кешу враховував ?lang=, бо той їде в query, але не враховував заголовок Accept-Language. Два клієнти на одному тарифі з однаковим тілом і різними мовами ділили один запис. Запит із Accept-Language: de повертав українську відповідь, у якій ще й було написано requestedLanguage: "uk". Мова тепер входить у ключ.

/v1/interpret/* більше не проходить через цей кеш. У нього є власний AI-кеш із годинним TTL і 50% знижкою на повтор, і саме він мав відповідати, але список винятків містив interpret замість interpret/*, тому першим завжди спрацьовував загальний. /v1/horoscope/* навпаки залишається тут: його обробник звертається до моделі на кожен запит, і цей кеш - єдине, що стоїть між повтором і другою генерацією.

Що зробити з вашим кодом. Нічого. Якщо ви обходили кеш, додаючи в тіло щось випадкове, щоб не платити двічі за ту саму відповідь, тепер це не потрібно.