#Быстрый старт

Здесь — кратчайший путь от нуля до первого работающего приёма. Мы соберём самую простую конфигурацию: Launch + push, то есть кнопка в карточке приёма и автоматический возврат протокола.

Перед началом прочитайте Обзор интеграции — без ментальной модели дальнейшее будет выглядеть набором несвязанных запросов.

Если у вас есть ИИ-агент с доступом к репозиторию, бо́льшую часть этой работы можно не делать руками: см. Интеграция с помощью ИИ-агента.

#Что понадобится

  • Возможность добавить кнопку в карточку приёма вашей МИС.
  • Возможность поднять HTTP endpoint, доступный нашим серверам по HTTPS.
  • Параметры подключения от Smartica (шаг 1).

Времени на первый сквозной тест обычно уходит несколько часов.

#Шаг 1. Получите параметры подключения

Это первый шаг, а не последний. Подключение не включается самостоятельно: пока Smartica не зарегистрирует вашу клинику, любые запросы будут возвращать 404, каким бы правильным ни выглядел URL.

Напишите на support@smartica.ai или через форму связи и передайте:

  • название МИС и клиники;
  • адреса тестового и рабочего окружений вашей МИС;
  • контакт разработчика;
  • какую схему авторизации вы хотите для входящих запросов от Smartica.

В ответ вы получите:

Что Зачем
platform Идентификатор клиники, подставляется в адреса запросов к Smartica
Точный SSO endpoint Адрес, по которому запрашивается ссылка для входа врача
API-ключ клиники Секрет, которым вы авторизуетесь в Smartica
Параметры окружения Адрес тестового стенда и сетевые требования

Подробности процесса — в статье Онбординг партнёра.

Ключ выдаётся на тестовую площадку: там вы отлаживаетесь, ничем не рискуя. Примеры ниже написаны для рабочего адреса — на тестовом отличается только хост.

Пока ждёте ответ, можно спокойно писать код шагов 2 и 3: он не зависит от выданных параметров.

#Шаг 2. Сделайте два endpoint в своей МИС

Smartica будет обращаться к вашей МИС по одному адресу двумя методами:

GET  /api/for-smartica/encounters/{encounter_id}   → отдать список полей бланка
PUT  /api/for-smartica/encounters/{encounter_id}   → принять заполненный протокол

Адрес выбираете вы сами — важно лишь согласовать его с нами. Оба метода защищаются Bearer-токеном поверх HTTPS: это схема по умолчанию. Для совместимости возможен HTTP Basic, а mTLS и подпись запроса требуют доработки — согласуйте их до начала разработки. Сравнение вариантов: Авторизация, ошибки и повторная доставка.

#GET — отдаёте контекст приёма

Smartica спрашивает: «какой это бланк и какие в нём поля?»

Ответ должен быть 200 и JSON:

{
  "encounter_id": "12345",
  "user_email": "doctor@clinic.ru",
  "template": "Кардиолог",
  "fields": [
    { "id": "complaints", "label": "Жалобы" },
    { "id": "diagnosis_primary", "label": "Диагноз (основной)" }
  ]
}

Здесь id — ваш технический ключ поля, а label — название, по которому Smartica поймёт, что в это поле класть. От качества label напрямую зависит качество заполнения, поэтому пишите их так, как их понимает врач.

Полный контракт: GET: контекст приёма.

#PUT — принимаете результат

Когда протокол готов, Smartica присылает:

{
  "encounter_id": "12345",
  "fields": [
    { "id": "complaints", "value": "Жалобы на головную боль..." },
    { "id": "diagnosis_primary", "value": "Гипертоническая болезнь I ст." }
  ],
  "transcript": "Полная стенограмма разговора..."
}

Ваша задача — разложить value по полям с соответствующими id и сохранить стенограмму.

Три правила, которые чаще всего нарушают:

  1. Поля, которых нет в массиве, не очищайте. Smartica присылает только те поля, для которых нашлось содержание. Пустой массив fields — валидная ситуация, стенограмму всё равно надо сохранить.
  2. Повторный такой же запрос не должен создавать вторую карточку. Обновляйте существующий приём по encounter_id. Повторы реально приходят: врач может дописать приём или нажать «отправить заново».
  3. Отвечайте 200 с JSON-телом. Не 204, не пустое тело, не HTML.

Полный контракт: PUT: запись протокола.

#Пример реализации

Ниже — минимальный рабочий вариант на Node.js с Express. Логика переносится на любой стек: важна структура, а не язык.

const express = require('express');
const app = express();

app.use(express.json({ limit: '5mb' }));

const crypto = require('crypto');

// Проверка Bearer-токена. Токен вы генерируете сами и сообщаете нам при подключении.
function requireSmarticaAuth(req, res, next) {
  const header = req.headers.authorization || '';
  const expected = `Bearer ${process.env.SMARTICA_INBOUND_TOKEN}`;

  // Сравнение за постоянное время, чтобы токен нельзя было подобрать по таймингу.
  const a = Buffer.from(header);
  const b = Buffer.from(expected);

  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).json({ message: 'Неверный токен' });
  }
  next();
}

// GET — отдаём бланк и его поля.
app.get('/api/for-smartica/encounters/:id', requireSmarticaAuth, async (req, res) => {
  const encounter = await db.findEncounter(req.params.id);

  if (!encounter) {
    return res.status(404).json({ message: 'Приём не найден' });
  }

  res.json({
    encounter_id: String(encounter.id),
    user_email: encounter.doctorEmail,
    template: encounter.formName,
    fields: encounter.formFields.map((f) => ({ id: f.key, label: f.title })),
  });
});

// PUT — принимаем заполненный протокол.
app.put('/api/for-smartica/encounters/:id', requireSmarticaAuth, async (req, res) => {
  const { encounter_id: bodyId, fields = [], transcript } = req.body;

  if (bodyId !== req.params.id) {
    return res.status(422).json({ message: 'ID в адресе и в теле не совпадают' });
  }

  const encounter = await db.findEncounter(req.params.id);
  if (!encounter) {
    return res.status(404).json({ message: 'Приём не найден' });
  }

  await db.transaction(async (tx) => {
    // Обновляем только пришедшие поля. Остальные не трогаем.
    for (const field of fields) {
      await tx.upsertProtocolField(encounter.id, field.id, field.value);
    }
    await tx.saveTranscript(encounter.id, transcript);
  });

  res.json({ encounter_id: String(encounter.id), status: 'saved' });
});

app.listen(3000);

Проверьте свои endpoint'ы курлом, ещё до подключения Smartica:

curl --fail-with-body --silent --show-error \
  --header 'Authorization: Bearer ЗАМЕНИТЕ-НА-ТОКЕН' \
  --header 'Accept: application/json' \
  'https://mis.example.ru/api/for-smartica/encounters/12345'

#Шаг 3. Добавьте кнопку «Smartica» и серверный SSO

Кнопка не должна вести на Smartica напрямую. Правильный поток такой:

  1. Браузер сообщает вашему бэкенду только ID текущего приёма.
  2. Бэкенд проверяет, что этот врач действительно имеет доступ к этому приёму.
  3. Бэкенд сам достаёт email и ФИО врача из своей базы — не принимает их от браузера.
  4. Бэкенд запрашивает у Smartica одноразовую ссылку.
  5. Бэкенд перенаправляет браузер на полученную ссылку.

Ключ клиники при этом никогда не попадает в JavaScript.

Запрос за ссылкой:

POST https://app.smartica.ai/api/v1/integrations/{platform}/sso/token
Authorization: Bearer <API-ключ клиники>
Content-Type: application/json
Accept: application/json

{
  "user_email": "doctor@clinic.ru",
  "user_full_name": "Иванов Иван Иванович",
  "encounter_id": "12345"
}

user_full_name передавайте всегда. Для уже существующего врача оно не требуется, но при самом первом входе без него мы не сможем завести аккаунт и вернём 422.

Ответ:

{
  "launch_url": "https://app.smartica.ai/launch?platform=…&encounter_id=12345&t=…",
  "expires_in": 60
}

Сразу откройте launch_url врачу — через редирект 302 или в новой вкладке. Ссылка одноразовая и живёт секунды, указанные в expires_in.

Не разбирайте и не собирайте этот URL самостоятельно, не сохраняйте параметр t и не логируйте ссылку целиком.

Пример на Node.js:

app.post('/encounters/:id/smartica-launch', requireDoctorAuth, async (req, res) => {
  const encounter = await db.findEncounter(req.params.id);

  // Врач берётся из сессии МИС, а не из тела запроса.
  if (!encounter || !canAccess(req.user, encounter)) {
    return res.status(403).json({ message: 'Нет доступа к приёму' });
  }

  const response = await fetch(process.env.SMARTICA_SSO_ENDPOINT, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SMARTICA_SSO_KEY}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify({
      user_email: req.user.email,
      user_full_name: req.user.fullName,
      encounter_id: String(encounter.id),
    }),
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    return res.status(502).json({ message: error.message || 'Smartica недоступна' });
  }

  const { launch_url: launchUrl } = await response.json();
  res.redirect(302, launchUrl);
});

Подробности, включая все коды ошибок: Launch URL и SSO.

#Можно обойтись без GET

Если передать в том же SSO-запросе поля template и fields, Smartica возьмёт бланк оттуда и не будет обращаться к вашему GET:

{
  "user_email": "doctor@clinic.ru",
  "user_full_name": "Иванов Иван Иванович",
  "encounter_id": "12345",
  "template": "Кардиолог",
  "fields": [
    { "id": "complaints", "label": "Жалобы" },
    { "id": "diagnosis_primary", "label": "Диагноз (основной)" }
  ]
}

Тогда из шага 2 остаётся реализовать только PUT. Это самый быстрый путь к работающей интеграции.

#Шаг 4. Прогоните один контрольный приём

Проверяйте по порядку — так сразу видно, на каком шаге что сломалось.

  1. Создайте тестовый приём с уникальным encounter_id.
  2. Вызовите свой GET курлом и убедитесь, что возвращается 200 и корректный JSON.
  3. Откройте карточку приёма в МИС и нажмите «Smartica».
  4. Убедитесь, что врач попал в Smartica без повторного ввода логина и пароля.
  5. Запишите короткий разговор, остановите запись и дождитесь обработки.
  6. Проверьте, что вам пришёл PUT, а в нём — значения полей и полная стенограмма.
  7. Отправьте тот же PUT повторно курлом и убедитесь, что в МИС остался один обновлённый приём, а не два.
  8. Добавьте ещё одну запись в тот же приём и проверьте, что результат обновился, а не продублировался.

Если что-то не сработало — Диагностика: если что-то не работает.

#Чеклист готовности

  • Получены platform, точный SSO endpoint и API-ключ клиники
  • encounter_id передаётся строкой и не переиспользуется для другого приёма
  • SSO вызывается только с бэкенда, ключ отсутствует во фронтенде и логах
  • GET возвращает стабильный template, уникальные id и понятные label
  • Email врача в SSO и в GET относится к одному и тому же человеку
  • PUT обновляет существующий приём и не создаёт дубликаты
  • Поля, отсутствующие в PUT, не затираются
  • Успешные ответы содержат JSON, 204 No Content не используется
  • Тексты ошибок не содержат стектрейсов, SQL и секретов

Расширенный чеклист перед боевым запуском — в Онбординге партнёра.

#Что дальше

← Все статьи: Интеграция с МИС Поиск по базе знаний