#Быстрый старт
Здесь — кратчайший путь от нуля до первого работающего приёма. Мы соберём самую простую конфигурацию: 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 и сохранить стенограмму.
Три правила, которые чаще всего нарушают:
- Поля, которых нет в массиве, не очищайте. Smartica присылает только те поля, для которых нашлось содержание. Пустой массив
fields— валидная ситуация, стенограмму всё равно надо сохранить. - Повторный такой же запрос не должен создавать вторую карточку. Обновляйте существующий приём по
encounter_id. Повторы реально приходят: врач может дописать приём или нажать «отправить заново». - Отвечайте
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 напрямую. Правильный поток такой:
- Браузер сообщает вашему бэкенду только ID текущего приёма.
- Бэкенд проверяет, что этот врач действительно имеет доступ к этому приёму.
- Бэкенд сам достаёт email и ФИО врача из своей базы — не принимает их от браузера.
- Бэкенд запрашивает у Smartica одноразовую ссылку.
- Бэкенд перенаправляет браузер на полученную ссылку.
Ключ клиники при этом никогда не попадает в 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. Прогоните один контрольный приём
Проверяйте по порядку — так сразу видно, на каком шаге что сломалось.
- Создайте тестовый приём с уникальным
encounter_id. - Вызовите свой
GETкурлом и убедитесь, что возвращается200и корректный JSON. - Откройте карточку приёма в МИС и нажмите «Smartica».
- Убедитесь, что врач попал в Smartica без повторного ввода логина и пароля.
- Запишите короткий разговор, остановите запись и дождитесь обработки.
- Проверьте, что вам пришёл
PUT, а в нём — значения полей и полная стенограмма. - Отправьте тот же
PUTповторно курлом и убедитесь, что в МИС остался один обновлённый приём, а не два. - Добавьте ещё одну запись в тот же приём и проверьте, что результат обновился, а не продублировался.
Если что-то не сработало — Диагностика: если что-то не работает.
#Чеклист готовности
- Получены
platform, точный SSO endpoint и API-ключ клиники -
encounter_idпередаётся строкой и не переиспользуется для другого приёма - SSO вызывается только с бэкенда, ключ отсутствует во фронтенде и логах
-
GETвозвращает стабильныйtemplate, уникальныеidи понятныеlabel - Email врача в SSO и в
GETотносится к одному и тому же человеку -
PUTобновляет существующий приём и не создаёт дубликаты - Поля, отсутствующие в
PUT, не затираются - Успешные ответы содержат JSON,
204 No Contentне используется - Тексты ошибок не содержат стектрейсов, SQL и секретов
Расширенный чеклист перед боевым запуском — в Онбординге партнёра.
#Что дальше
- Нужен сценарий записи без МИС: Import: импорт из Smartica.
- МИС в закрытом контуре и входящие запросы невозможны: посмотрите режим
pullв той же статье. - Хотите поручить черновую реализацию ИИ-агенту: Интеграция с помощью ИИ-агента.
- Нужны точные лимиты: Лимиты и ограничения.