Интеграция тендеров: закупки в CRM и своих системах
Схема одна для любой CRM: фоновый воркер на вашем сервере читает ленту закупок по сохранённому курсору и создаёт из новых объектов лиды или сделки штатным API вашей CRM. Напрямую из браузера обращаться к API нельзя — он работает только server-to-server, CORS отключён, а токен даёт доступ к балансу и не должен покидать сервер.
Схема интеграции
Fraim.ru API ──► ваш воркер ──► API вашей CRM
(лента по (расписание, (лиды, сделки,
курсору) дедупликация) задачи)
│
└──► ваша БД: последний курсор + обработанные id/versionТри элемента, и все три обязательны. Воркер нужен, потому что API server-to-server. Своя таблица нужна, чтобы помнить курсор и уже обработанные версии. Расписание нужно, потому что закупки появляются постоянно, а не по запросу пользователя.
Почему нельзя обращаться из CRM напрямую
- CORS отключён. API рассчитан на межсерверное взаимодействие; браузерный запрос из виджета или веб-хука фронтенда просто не пройдёт.
- Токен — это доступ к балансу. Ключ, попавший в браузер или в мобильное приложение, может быть использован кем угодно и потратит ваши деньги.
- Нужна память между запусками. Курсор и обработанные версии где-то должны храниться — у CRM для этого нет подходящего места.
Активных токенов может быть до 20 — заведите для интеграции отдельный, чтобы отозвать его, не задев остальные. Начинать удобнее с тестового fraim_test_…: песочница не списывает баланс, а код при переходе в продакшн не меняется — меняется только токен.
Воркер: рабочий пример
import os, time, requests
API = "https://public.fraim.ru/api/v2"
H = {"Authorization": f"Bearer {os.environ['FRAIM_TOKEN']}"}
FILTER = {
"keywords": ["поставка оборудования"],
"exception_keywords": ["монтаж"],
"regions": [1, 2],
"price": {"from": 300000},
"status": ["active"],
"limit": 100,
}
def tick(cursor):
"""Один проход. Возвращает новый курсор — его нужно сохранить."""
body = {"cursor": cursor} if cursor else FILTER
r = requests.post(f"{API}/tenders/query", json=body, headers=H, timeout=60)
if r.status_code == 202: # фоновый сбор — повторить позже
return cursor
if r.status_code == 429: # лимит частоты
time.sleep(int(r.headers.get("Retry-After", 60)))
return cursor
if r.status_code == 409: # лента пересобрана: срез уже в теле
body = r.json()["detail"]
push_to_crm(body["results"])
return body["next_cursor"]
if r.status_code == 402: # баланс кончился — не терять курсор
notify("Fraim.ru: пополните баланс")
return cursor
r.raise_for_status()
data = r.json()
push_to_crm(data["results"])
return data["next_cursor"]Курсор возвращается из каждой ветки, включая ошибочные: потерянный курсор означает повторное чтение выборки с начала — и повторную оплату новых версий объектов.
Дедупликация и обновления
Закупка живёт: меняются сроки, документация, статус. В ленту она приходит снова — с тем же id и новой version. Правило простое:
- Новый
id— создать карточку в CRM. - Тот же
id, новаяversion— обновить существующую, а не плодить дубль. - Та же пара
id + version— пропустить: вы это уже обрабатывали.
Хранить обработанные пары нужно у себя: доставка считается выполненной на стороне API независимо от того, что с объектом сделала ваша интеграция.
Частота опроса
Раз в 5–15 минут закрывает практически любую воронку. Лента по курсору отдаёт всё накопившееся с прошлого раза, поэтому между опросами ничего не теряется — частота влияет только на задержку, а не на полноту.
- 100 запросов в минуту на аккаунт — ежеминутный опрос тратит один из ста.
- 20 запросов в секунду по IP — ограничение на всплески; параллельная выгрузка в несколько потоков упирается в него раньше, чем в минутный лимит.
- Пустой
resultsс непустымnext_cursor— «сейчас нового нет», курсор рабочий, опрос бесплатен.
Расход на постоянный мониторинг
Счёт зависит не от частоты опроса, а от того, сколько закупок реально проходит через ваш фильтр. Опросы без новых объектов и повторы той же версии бесплатны; платится только первая доставка версии — от 0,75 ₽ за объект.
Отсюда практический совет: сужайте фильтр, а не частоту. Минус-слова и вилка цены снимают со счёта больше, чем любые ухищрения с расписанием, и одновременно избавляют менеджеров от нерелевантных карточек.