Водители по всей России отмечают на карте Fbenz, где есть бензин и очереди. Partner API отдаёт эти статусы вашему приложению. Или сразу целую карту под вашим брендом.
Если у вас своя карта, берите чистый JSON. Если карты нет, встройте готовый виджет.
Забирайте координаты АЗС и статусы топлива, рисуйте на своей карте в своём стиле. Подходит банкам с готовым приложением.
fb_live_…, запросы сервер-серверГотовая карта Fbenz без нашего брендинга: кластеры, фильтр по топливу, карточки АЗС. Встраивается одним iframe за вечер.
pk_live_…, привязан к вашим доменамИнструкция рассчитана на человека, который никогда не работал с API. Если что-то не получается на любом шаге, просто напишите нам и мы поможем.
fb_live_k3J9…, ваш пропуск в API.
Чтобы получить ключ, напишите нам: контакт в самом низу страницы. Условия подключения обсудим в переписке.
Ключ показывается один раз при выдаче, поэтому сразу сохраните его в надёжное
место и обращайтесь с ним как с паролем.powershell и нажмите Enter.
macOS: откройте Spotlight (Cmd+Пробел) и наберите «Терминал». Ставить ничего
не нужно, всё уже есть в системе.Ключ выдаёт команда Fbenz при подключении: напишите нам (контакт внизу страницы), обсудим условия и аудиторию вашего продукта. Ключ показывается один раз: скопируйте его сразу и храните как пароль.
Подставьте свой ключ в заголовок Authorization.
curl --get "https://fbenz.ru/api/v1/partner/stations" \
--data-urlencode "city=Москва" \
-H "Authorization: Bearer fb_live_ВАШ_КЛЮЧ"
Флаг --data-urlencode обязателен: он кодирует
кириллицу в названии города. Без него curl отправит битые байты, город не совпадёт
и придёт 404 city-not-found (кодов 400 в этом API нет,
ошибки валидации это всегда 422).
Invoke-RestMethod -Uri "https://fbenz.ru/api/v1/partner/stations?city=Москва" -Headers @{ Authorization = "Bearer fb_live_ВАШ_КЛЮЧ" }
Проверьте себя. В ответе должно быть поле count
с числом станций и список stations. Если пришла ошибка 401
invalid-key, ключ вставлен не целиком: скопируйте его заново без пробелов.
Если пришло 404 city-not-found, название города ушло без кодировки:
возьмите команду из примера полностью, не перенабирайте её руками.
У каждой станции есть координаты, бренд, адрес и блок fuel
с текущим статусом.
{
"stations": [{
"id": "n1234567890",
"lat": 55.7558,
"lon": 37.6173,
"brand": "Лукойл",
"name": "Лукойл",
"addr": "Ленинградский пр-т, 63",
"fuel": {
"status": "yes",
// что есть и чего нет
"fuels_now": "92,95,ДТ",
"fuels_no": "100",
// цены за литр, если водители их указали
"prices": "92=58.4,95=63.2",
// очередь машин: "", "0-5", "5-10", "10-20", "20+"
"queue": "5-10",
// лимит литров в одни руки (null = нет)
"limit_l": 20,
// особенности оплаты
"no_cash": false,
"fuel_cards_only": false,
// подтверждений от водителей
"confirmations": 3,
// независимых живых устройств за этим статусом
"real_count": 3,
// true = статус держится на импортированных данных, а не на отметках людей
"seeded": false,
"updated": "2026-07-16 09:41:07",
// отметка есть, но подтверждений ей пока не хватает
"pending_status": "no",
"pending_count": 1,
// published · unconfirmed · conflict · no_data
"resolution_reason": "unconfirmed",
// не null только при resolution_reason = conflict
"conflict": null
}
}],
"count": 412,
"as_of": "2026-07-16 09:45:00",
"stale_after_hours": 24
}
В поле fuel.status тот же статус, который видят водители на карте Fbenz. Показывайте его как есть: этих пяти значений хватит для любого интерфейса.
Простой путь: качайте всю страну целиком из /stations/all
раз в 10-15 минут с заголовком If-None-Match. Экономный путь:
передавайте as_of из прошлого ответа в
/stations/delta?updated_since= раз в 1-5 минут. Подробности
в разделе «Свежесть и лимиты».
Все методы живут под /api/v1/partner, ключ передаётся
в заголовке Authorization: Bearer <ключ>.
Полная спецификация: openapi.json.
| Задача | Метод | Что вернёт |
|---|---|---|
| Получить АЗС города или области карты | GET/stations?city= или ?bbox= |
До 4000 станций со статусами. Радиус города задаёт параметр radius_km (до 60 км). Понимает поиск и фильтры. Если область больше и список обрезан, в ответе придёт "truncated": true: берите область меньше или /stations/all. |
| Выгрузить всю Россию разом | GET/stations/all |
Все станции страны со статусами: страницы по 10 000, курсор в next_cursor, параметром limit можно поднять страницу до 30 000 и забрать всё одним запросом. |
| Следить за изменениями | GET/stations/delta?updated_since= |
Только станции, у которых статус менялся с указанного момента. Курсор берите из поля as_of. |
| Открыть карточку одной АЗС | GET/stations/{id} |
Станция и её текущий статус. |
| Список городов для меню | GET/cities?q= |
По умолчанию все города России разом (~1200, отсортированы по населению)
с координатами и зумом: удобно один раз забрать под своё меню. ?q= ищет по названию,
?limit= ограничивает выдачу топ-N. |
| Посмотреть свой расход | GET/usage |
Запросы за сегодня, лимиты, разбивка по дням и методам.
Глубина истории задаётся параметром ?days= (по умолчанию 7, до 365). |
| Отправить отметку вашего юзера | POST/stations/{id}/report |
Принимает статус от конечного пользователя вашего приложения: есть, мало, нет, очередь, марки, цены. Отметка идёт по тем же правилам, что у водителей на fbenz.ru. Детали в разделе «Отчёты юзеров». |
| Запросить новые лимиты | POST/limit-requests |
Создаёт заявку команде Fbenz. Тело:
{"requested_rpm": 600, "requested_daily": 2000000, "comment": "..."}.
Статус смотрите в кабинете партнёра
или GET-запросом по тому же пути. |
Не хотите фильтровать на своей стороне: попросите сервер. Все параметры
работают на /stations и /stations/all, считаются в базе до
отдачи, поэтому ответ никогда не теряет подходящие станции из-за лимита страницы.
| Параметр | Пример | Что делает |
|---|---|---|
q | q=лукойл ленина |
Поиск по названию, бренду и адресу. Каждое слово должно найтись. Слова «95», «дт», «дизель» внутри запроса сами включают фильтр по топливу, как в поиске на fbenz.ru. |
status | status=yes,low |
Только станции с этими видимыми статусами. Значения: yes, low, no, queue, nodata. Протухший статус честно считается за nodata, старое значение не подставляется. |
fuel | fuel=95 |
Станции, где сейчас отмечена эта марка. Значения: 92, 95, 98, 100, ДТ (можно писать dt или дизель). Несколько через запятую: станция должна иметь все. |
brand | brand=Лукойл |
Точное имя бренда, регистр не важен. Для нестрогого поиска бренда используйте q. |
has_price | has_price=true |
Только станции со свежими ценами. Честное предупреждение: цены водители указывают редко, выборка будет маленькой. |
queue | queue=0-5,5-10 |
Фильтр по длине очереди. Бакеты: 0-5, 5-10, 10-20, 20+. |
fresh_h | fresh_h=3 |
Статус не старше N часов (от 1 до 24). Жёстче стандартного суточного окна. |
sort | sort=distance |
Ближние к центру города или области первыми. Только на /stations. |
curl --get "https://fbenz.ru/api/v1/partner/stations" \
--data-urlencode "city=Москва" \
--data-urlencode "fuel=95" \
--data-urlencode "brand=Лукойл" \
--data-urlencode "status=yes,low" \
-H "Authorization: Bearer fb_live_ВАШ_КЛЮЧ"
/stations/delta фильтров нет сознательно: отфильтрованная
дельта скрыла бы момент, когда станция перестала подходить под фильтр, и ваша копия
зависла бы на старом статусе.Пользователи вашего приложения отмечают наличие топлива так же, как водители на fbenz.ru: та же форма, те же правила, тот же вес голоса. Ваш бэкенд просто пересылает отметку одним POST-запросом с секретным ключом.
curl -X POST "https://fbenz.ru/api/v1/partner/stations/n1234567890/report" \
-H "Authorization: Bearer fb_live_ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"status": "yes",
"user_ref": "user-42",
"fuels": ["92", "95"],
"prices": {"95": 61.5},
"lat": 55.7558, "lon": 37.6173,
"external_id": "report-8f1c"
}'
| Поле | Обязательно | Что значит |
|---|---|---|
status | да | yes есть, low мало, no нет, queue очередь. |
user_ref | да | Постоянный id юзера на вашей стороне (любая строка до 128 символов). Мы храним только его хеш. Один юзер = один голос, поэтому id должен быть стабильным, а не случайным на каждый запрос. |
lat + lon | нет | GPS юзера в момент отметки (плюс опционально acc_m, точность в метрах). С координатами рядом со станцией голос идёт с полным весом. Без координат отметка принимается, но с пониженным весом, и для публикации нужно подтверждение второго юзера. |
fuels, fuels_no | нет | Какие марки есть и каких нет: ["92","95","ДТ"]. Допустимы только 92, 95, 98, 100, ДТ (можно dt/дизель), иначе 422. |
queue, prices, limit_l, no_cash, fuel_cards_only, text | нет | Детали как в форме на fbenz.ru: очередь (0-5…20+), цены, лимит литров, «не берут наличные», «только топливные карты», комментарий до 200 символов. |
external_id | нет | Ваш id отметки для повторов. Повторный POST с тем же external_id не создаст дубль, в ответе будет "duplicate": true. Тот же флаг придёт, если один и тот же пользователь повторит СВОЁ ЖЕ мнение по той же АЗС в пределах 5 минут: это не ошибка, отметка просто не удваивается. |
observed_at | нет | Когда юзер реально нажал кнопку (UTC), если отметка ехала к вам с задержкой. |
express | нет | «Топливо кончилось прямо сейчас»: тот же пользователь может переотметить эту АЗС уже через 2 минуты вместо обычных 20. Для случая, когда человек стоит на заправке и бензин закончился у него на глазах. |
Что вернётся: "accepted": true, поле "remote", блок
published с итогом пересчёта и блок fuel — та же самая
витрина станции, что приходит в чтении. Перечитывать станцию после записи не нужно:
обновляйте карточку у себя прямо из ответа.
"remote": true значит, что голос принят без гео-подтверждения (координат нет,
они далеко от станции или движение юзера физически невозможно): вес такого голоса ниже,
и для публикации нужно подтверждение второго юзера. Отметка не обязана менять статус
мгновенно: у станции те же пороги доверия, что и для водителей.
Статус выходит на карту, когда за него набралось нужное число независимых устройств. По умолчанию правило такое:
| Отметка | Сколько устройств нужно | Почему так |
|---|---|---|
yes, low, queue с геопозицией у АЗС |
одно | человек стоит на заправке и видит топливо своими глазами |
no (топлива нет) |
два, если рядом есть свежие отметки других людей | ложное «нет» уводит водителя от живой заправки, это самая дорогая ошибка на карте |
любая отметка без геопозиции ("remote": true) |
два | без координат отметку нельзя проверить физически |
Пока устройств не хватило, статус станции остаётся прежним, а сама отметка живёт
в полях pending_status и pending_count. Они приходят
в блоке fuel в каждом чтении: карточка АЗС, выборка по прямоугольнику
и дельта. По ним рисуется плашка «ждёт подтверждения» и кнопка «подтверждаю»,
как это сделано в нашем виджете.
Отдельного метода для подтверждения нет и не нужно. Подтверждение это
обычная отметка тем же статусом от другого пользователя: тот же
POST /stations/{id}/report, тот же status, другой
user_ref. Как только устройств хватило, статус публикуется и
pending_count обнуляется. Один и тот же user_ref подтвердить
сам себя не может: у устройства одно мнение о станции, повторная отметка заменяет
предыдущую, а не удваивает её.
Два только что созданных пользователя не подтверждают друг друга. Устройства, впервые увиденные в пределах 10 минут, считаются одним человеком: это защита от накрутки, когда «толпу» заводят одной пачкой. На практике это заметно только в первые минуты жизни новой интеграции; дальше у ваших пользователей разный возраст и подтверждение проходит. Если ждать не хочется, порог снимается настройкой ниже.
Правило настраивается под вас. Для вашего ключа порог можно снять совсем
(публиковать с первой отметки) или, наоборот, поднять до трёх и больше устройств.
Настройка действует только на отметки, пришедшие через ваш ключ, и никогда не блокирует
отметки обычных водителей в приложении. Текущее значение приходит в
GET /widget/config полем confirm_min: 0 это
общее правило из таблицы выше. Поменять порог можно по запросу, напишите нам.
| Ошибка | Почему | Что делать |
|---|---|---|
403 reports-disabled | приём отметок для вас не включён | Напишите нам, включим тумблером без редеплоя. |
429 station-cooldown | этот юзер уже отмечал эту станцию недавно | Штатно. Одна отметка на станцию раз в 20 минут, покажите юзеру «уже учли». |
429 user-rate-limited | часовой или суточный кап отметок юзера | Кап на юзера, не на вас. Повторите позже. |
429 new-users-limit | суточный лимит НОВЫХ отмечающих юзеров | Известные юзеры продолжают отмечать. Если лимит мал для вашего запуска, напишите нам, поднимем. |
422 too-far | координаты далеко, а приём без GPS для вас выключен | По умолчанию далёкие координаты принимаются как "remote": true с пониженным весом, и этой ошибки вы не увидите. Получили её: отправьте без lat/lon или напишите нам. |
Данные собирают сами водители: отметка появляется в API
через несколько минут. У каждого статуса есть updated,
а у каждого ответа as_of.
stale_after_hours в каждом ответе).
Устаревший статус отдаётся как nodata./stations. Его ETag считается
по видимому состоянию: если ничего не изменилось, вернётся 304, почти бесплатный.Качайте /stations/all раз в 10-15 минут с заголовком
If-None-Match. Ничего не менялось: придёт 304, почти бесплатный.
Менялось: забирайте страницы по next_cursor (или всё одним
запросом с limit=30000). ETag тут общий на весь дамп, поэтому
протухания и пересчёты он ловит сам, дельты и часовые снапшоты в этой схеме
не нужны.
| Что | Как часто | Зачем |
|---|---|---|
/stations/delta | раз в 1-5 минут | Свежие изменения. Курсор берите из as_of прошлого ответа. |
/stations полный, с If-None-Match | раз в час | Ловит устаревания и пересчёты. Без изменений вернётся 304, почти бесплатный. |
Лимиты не про оплату запросов, они защищают сервис от перегрузки: у каждого партнёра свой лимит в минуту и суточная квота. Текущее состояние приходит в заголовках каждого ответа:
// лимит и остаток в текущей минуте
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
// unix-время сброса минуты
X-RateLimit-Reset: 1784192460
// суточная квота и остаток
X-RateLimit-Daily-Limit: 50000
X-RateLimit-Daily-Remaining: 49212
При превышении придёт 429 с заголовком Retry-After:
подождите указанное число секунд и повторите. Квота сбрасывается в 00:00 UTC.
Если нужны лимиты выше, отправьте заявку с новыми значениями из кабинета партнёра. Ответим в течение рабочего дня, одобренные лимиты применятся сами.
Запросы виджет-ключом по bbox отдаются из общего кеша: одинаковый
прямоугольник считается один раз на всех ваших зрителей. В ответе есть заголовок
X-Fbenz-Cache: hit|miss. Принятая отметка сразу выбрасывает из кеша все
прямоугольники, накрывающие эту АЗС, поэтому после записи следующее чтение уже свежее.
Секретный ключ читает мимо кеша и получает точный прямоугольник без квантования, поэтому
ETag у секретного и виджет-ключа на один и тот же bbox разные — это нормально.
Кеш настраивается под вас. Его можно выключить совсем (тогда каждый запрос считается заново и всегда отдаёт последнее состояние) или задать свою длительность в секундах, от нуля до часа. Дольше кеш — быстрее карта и меньше нагрузка, короче кеш — быстрее видно чужие свежие отметки; свои собственные отметки видны сразу в любом случае, потому что запись сбрасывает кеш по своей АЗС. По умолчанию кеш включён с общей длительностью сервиса. Поменять — напишите нам.
/stations/all отдаёт страницы минимум по 100 записей:
limit=1 вернёт 422. Максимум 30000.queue=20+ надо писать как queue=20%2B:
плюс в query-строке означает пробел. В JSON-теле отчёта плюс пишется как есть.city= — это поиск, а не точное совпадение: опечатка молча найдёт
ближайший город. Что именно нашлось, всегда видно в поле city ответа.resolution_reason описывает ЖИВОЙ пересчёт по отметкам. Показанный
status может при этом прийти из импортированных данных, поэтому пара
«status: yes + resolution_reason: unconfirmed» законна: на карте старое значение,
а свежая отметка ждёт подтверждения.Кластеры, фирменные маркеры брендов АЗС, фильтр по топливу, карточки станций. Логотипа Fbenz нигде нет. Один тег HTML.
<iframe
src="https://fbenz.ru/widget/?key=pk_live_ВАШ_КЛЮЧ&city=Москва"
width="100%" height="480" style="border:0"
allow="geolocation; fullscreen" title="Наличие топлива на АЗС"></iframe>
Куда вставить этот тег. Свой сайт: в HTML нужной страницы,
например перед закрывающим </body>. Tilda: блок T123 «HTML-код».
WordPress: блок «Пользовательский HTML». Bitrix: компонент «HTML-код». После вставки
откройте страницу: карта должна появиться сразу.
| Параметр | Значения | Что делает |
|---|---|---|
key | pk_live_… |
Ваш публикуемый ключ. Обязательный. |
city | название города | С какого города открыть карту. |
lat + lon | координаты | Открыть карту с точки (вместо city). Опционально zoom (по умолчанию 12). |
theme | light · dark · auto |
Стартовая тема, auto следует за системной. На карте есть кнопка
переключения: выбор пользователя запоминается в его браузере. Рядом кнопка геолокации, для неё
нужен атрибут allow="geolocation" на iframe, и кнопка «во весь экран» — она
появляется, только если в теге iframe есть allow="fullscreen" и браузер
поддерживает полноэкранный режим (на iOS Safari его для карты нет, там кнопка сама прячется). |
fuel | 92 · 95 · 98 · 100 · ДТ |
Сразу включить фильтр по топливу. |
nofilter | 1 |
Спрятать панель фильтров. |
nosearch | 1 |
Спрятать поиск. По умолчанию в карте живёт поиск как на fbenz.ru: город, АЗС по названию или бренду, слова «95» и «дт» включают фильтр топлива. |
themelock | 1 |
Тема берётся только из адреса iframe: кнопка темы внутри карты прячется, сохранённый выбор зрителя не участвует. Для витрин и конфигураторов, где темой управляет ваша страница. |
gestures | cooperative · greedy |
Как карта делит жесты со страницей. Внутри iframe по умолчанию cooperative: страница скроллится одним пальцем сквозь карту, карта двигается двумя, колесо зумит с Ctrl (с подсказками). При прямом открытии и в вебвью по умолчанию greedy: карта забирает все жесты. Параметр фиксирует режим вручную. |
fb_live_… во фронтенд
не вставляйте никогда: он только для запросов с сервера.Как именно проверяется домен: разрешив bank.ru, вы
автоматически разрешаете и его поддомены (www.bank.ru,
lk.bank.ru). Похожий чужой домен вроде bank.ru.evil.com
не проходит. Запрос вообще без ссылающейся страницы (обращение с сервера, не из браузера)
проходит: привязка защищает от копирования ключа на чужой сайт, это не криптография.
В виджете карточка станции собрана один в один с приложением: цветная плашка статуса с иконкой, крупный заголовок, строка «сколько подтверждений и когда обновлено», значок «данные устарели», блок «Это всё ещё актуально?» с кнопками «Да, подтверждаю» и «Изменилась», плашки «ждёт подтверждения» и «данные расходятся», марки топлива, цены за литр, лимит литров и способы оплаты. Ничего рисовать самому не нужно.
Если вы делаете свою карту на JSON API, тот же экран собирается из полей блока
fuel: status и queue дают заголовок,
confirmations и updated — строку доверия, seeded
отвечает на вопрос «это отметки людей или импорт», real_count показывает,
сколько независимых устройств за статусом, pending_status с
pending_count рисуют ожидание подтверждения, а conflict
(приходит только при resolution_reason: "conflict") даёт обе спорящие
версии с их числом устройств.
Карта подстраивается не под «телефон или десктоп», а под то, сколько места ей дал ваш контейнер. Проверено на пятнадцати размерах от 240×160 (крошечная карточка в списке) до 1600×900: ничего не наезжает друг на друга, полосы прокрутки внутри виджета нет, карточка АЗС и форма отметки всегда помещаются целиком.
Одна практическая мелочь: у тега iframe обязательно задайте высоту.
Без неё браузер поставит свои 150px, и карта окажется в узкой полоске. Ширину можно
смело ставить в 100%, а высоту — хоть в пикселях, хоть в
aspect-ratio, хоть на весь экран: раскладка пересоберётся сама, ваши стили
для этого не нужны.
В карточке каждой АЗС есть кнопка «Обновить статус»: зритель отмечает
есть топливо, мало, очередь или нет, и какие марки в наличии. Отметка уходит
через ваш публикуемый ключ и проходит те же проверки, что и отметки в приложении
fbenz.ru: близость к АЗС (виджет спросит геолокацию, для неё нужен
allow="geolocation" на iframe), защита от накрутки, кулдаун. Отметка
без точной геопозиции принимается с меньшим весом и ждёт подтверждения рядом с АЗС.
Кнопку можно выключить для вашего ключа: приём отметок это тумблер в вашем тарифе,
напишите нам. Устройство зрителя виджет помнит анонимно, без входа и регистрации.
Если по АЗС уже есть отметка, которой не хватает подтверждений, виджет показывает её отдельной плашкой с кнопкой «Подтверждаю»: один тап, и статус выходит на карту. Автору его собственной отметки кнопка не показывается, подтвердить себя нельзя. Подробнее про пороги написано в разделе про отметки выше.
Скопируйте пример на своём языке: он забирает справочники, держит данные свежими через дельты и правильно реагирует на 304 и 429. Это весь код, который нужен для боевой интеграции.
Города меняются редко: заберите список один раз и обновляйте, например, раз в сутки.
По умолчанию приходят все города России разом. Единственная зависимость:
pip install requests. В обоих блоках поменяйте ключ на свой, больше
менять ничего не нужно.
Если Python ещё не установлен: скачайте его с python.org, при установке отметьте
галочку «Add Python to PATH». Потом сохраните код ниже в файл app.py
и запустите в терминале команду python app.py.
import requests
BASE = "https://fbenz.ru/api/v1/partner"
KEY = "fb_live_ВАШ_КЛЮЧ" # храните в секретах, не в коде
H = {"Authorization": f"Bearer {KEY}"}
# все города страны одним запросом (для меню, поиска, стартового экрана)
cities = requests.get(f"{BASE}/cities", headers=H, timeout=15).json()["cities"]
# полный снапшот всех АЗС страны (страницы по 10 000)
stations, cursor = [], ""
while True:
r = requests.get(f"{BASE}/stations/all",
params={"cursor": cursor}, headers=H, timeout=60).json()
stations += r["stations"]
cursor = r.get("next_cursor") or ""
if not cursor:
break
as_of = r["as_of"] # курсор для дельт, шаг 2
print("станций загружено:", len(stations))
Опрашивайте /stations/delta и накладывайте изменения на свою копию.
При 429 подождите Retry-After секунд, это штатная ситуация, данные не теряются.
import time
by_id = {st["id"]: st for st in stations}
while True:
r = requests.get(f"{BASE}/stations/delta",
params={"updated_since": as_of}, headers=H, timeout=30)
if r.status_code == 429: # упёрлись в лимит: ждём и повторяем
time.sleep(int(r.headers.get("Retry-After", "30")))
continue
r.raise_for_status()
data = r.json()
for st in data["stations"]: # обновляем только изменившиеся
by_id[st["id"]] = st
as_of = data["as_of"]
print("обновлено станций:", len(data["stations"]))
time.sleep(120) # раз в 2 минуты достаточно
То же самое на Node.js, без единой зависимости. Этот блок самодостаточный:
скопируйте в app.js, поменяйте ключ, запустите node app.js.
const BASE = "https://fbenz.ru/api/v1/partner";
const KEY = "fb_live_ВАШ_КЛЮЧ"; // поменяйте на свой
const H = { Authorization: "Bearer " + KEY };
const byId = new Map(); // ваша копия данных
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
// таймаут на каждый запрос: сбой сети не подвесит ваш сервис (аналог timeout= в requests)
const get = (path) => {
const c = new AbortController();
const t = setTimeout(() => c.abort(), 30_000);
return fetch(BASE + path, { headers: H, signal: c.signal }).finally(() => clearTimeout(t));
};
async function main() {
// старт: полный снапшот всей страны, страницы по 10 000
let cursor = "", asOf = "";
do {
const r = await get("/stations/all?cursor=" + encodeURIComponent(cursor));
const data = await r.json();
for (const st of data.stations) byId.set(st.id, st);
asOf = data.as_of;
cursor = data.next_cursor || "";
} while (cursor);
console.log("станций загружено:", byId.size);
// дальше по кругу: только изменения
while (true) {
await sleep(120_000); // раз в 2 минуты достаточно
const r = await get("/stations/delta?updated_since=" + encodeURIComponent(asOf));
if (r.status === 429) { // лимит: подождать и повторить
await sleep(1000 * (+r.headers.get("retry-after") || 30));
continue;
}
const data = await r.json();
for (const st of data.stations) byId.set(st.id, st);
asOf = data.as_of;
console.log("обновлено станций:", data.stations.length);
}
}
main();
--data-urlencode в curl,
params= в requests, encodeURIComponent в JS делают это сами.Retry-After секунд и повтор. Не ретраить в цикле без паузы.If-None-Match: ответ 304 не тратит трафик.nodata показывается как «нет данных», не как «топлива нет»: это разные вещи.| Вопрос | Ответ |
|---|---|
| Есть ли тестовая среда или тестовый ключ? | Отдельной песочницы нет и не нужно: ваш боевой ключ и есть тестовый. Чтение данных ничего не ломает, а отчёты проходят те же проверки, что и отметки в приложении. |
| Нужно ли настраивать CORS? | Нет. Секретный ключ ходит только сервер-сервер, браузер в этой схеме к нам
не обращается. Из браузера работает только виджет со своим pk_live_ ключом. |
| Потеряли ключ, что делать? | Восстановить нельзя (мы храним только хэш), но это не страшно: попросите новый, переключитесь, старый отзовём. Простоя не будет, оба работают одновременно. |
Почему у станции статус nodata? |
Свежих отметок нет: статус живёт 24 часа. Это нормально для маленьких городов ночью. Показывайте «нет данных» и серый цвет. |
| Виджет на сайте пишет «не разрешён для этого сайта» | Домен сайта не привязан к ключу. Напишите менеджеру, какие домены добавить (включая поддомены, если они разные). |
| Кнопка геолокации в виджете ничего не делает | На вашем теге iframe нет атрибута allow="geolocation": браузер
блокирует доступ к геопозиции внутри iframe без него. |
| Как заранее понять, что мы упираемся в лимиты? | Следите за X-RateLimit-Remaining и X-RateLimit-Daily-Remaining
в ответах или за графиком в кабинете. Заявку на новые лимиты можно отправить в любой момент. |
| Сколько это стоит? | Условия обсуждаем с каждым партнёром отдельно, под продукт и аудиторию. Напишите нам, контакт внизу страницы. |
Формат один: RFC 9457 (application/problem+json).
В type код для программы, в detail объяснение для человека.
В каждой ошибке есть request_id: назовите его нам, и мы найдём ваш
запрос в логах за минуту.
{
"type": "https://fbenz.ru/problems/rate-limit-exceeded",
"title": "Rate limit exceeded",
"status": 429,
"detail": "Minute quota of 120 requests exhausted; retry after 37s.",
"limit": 120,
"window": "60s",
// назовите этот id в саппорте
"request_id": "1a2b3c4d5e6f"
}
| Код | Тип | Что делать |
|---|---|---|
401 | invalid-key |
Ключ не подошёл или отозван. Проверьте, что скопировали ключ целиком. Если потеряли, запросите новый. |
403 | domain-not-allowed · ip-not-allowed · secret-key-required · partner-disabled |
Ключ используется не оттуда: виджет-ключ работает только с ваших доменов, секретный только с ваших IP (если включена привязка). partner-disabled значит, что доступ партнёра выключен, напишите вашему менеджеру Fbenz. |
422 | bad-bbox · bad-timestamp · missing-area · window-too-old |
Параметры не прочитались, формат указан в detail. window-too-old значит, что окно дельты больше 48 часов: заберите полный снапшот /stations. |
429 | rate-limit-exceeded · daily-quota-exceeded |
Упёрлись в лимит. Подождите Retry-After секунд. Если растёте, запросите новые лимиты в кабинете. |
503 | upstream-unavailable |
Короткий сбой на нашей стороне. Повторите через несколько секунд, данные не потеряются. |
fb_live_… секретный: все методы API, только
сервер-сервер. Храните его как пароль от базы.pk_live_… публикуемый: только виджет и только
ваши домены. Можно спокойно вставлять в HTML.Свой расход, лимиты и заявки смотрите в кабинете партнёра. Вход по секретному ключу, без регистрации.