ExodeAPI — типизированный HTTP-клиент, покрывающий эндпоинты /saas/v2/*. Предназначен для сервера (Node.js),
инкапсулирует аутентификацию, сериализацию query/тела, обработку ошибок и (опционально) валидацию ответов по
zod-схемам.
Полная REST-спецификация (request/response, коды ошибок) — в разделе Exode API .
SDK — типизированная обёртка над тем же контрактом; типы выведены из тех же серверных zod-схем.
Инициализация
import { ExodeAPI } from '@exode-team/sdk/api'
const exodeApi = new ExodeAPI ({
sellerId: 1 ,
schoolId: 1 ,
token: process . env . EXODE_TOKEN ! ,
baseUrl: 'https://api.exode.biz/saas/v2' , // default
timeout: 30_000 , // default 30s
})
Параметры конфигурации
Идентификатор продавца. Передаётся в заголовке Seller-Id.
Идентификатор школы. Передаётся в заголовке School-Id.
API-токен сервисного пользователя. Передаётся в Authorization: Bearer <token>.
Базовый URL API. По умолчанию https://api.exode.biz/saas/v2.
Таймаут запроса в миллисекундах. По умолчанию 30000. При превышении — ExodeAPIError с cause: "Timeout" (code 408).
Храните токен в переменных окружения. Никогда не коммитьте токены и не передавайте на клиент — ExodeAPI
работает только на сервере.
Доступные ресурсы
Клиент предоставляет namespace school с семью ресурсами:
school.user CRUD пользователей, состояние (state), удаление, токены авторизации.
school.group Список групп, массовое добавление/удаление участников.
school.course Список курсов и прогресс участников по курсу.
school.productAccess Список доступов к продуктам.
school.invoice Список счетов.
school.form Макеты форм и значения кастомных полей.
school.queryExport Генерация и опрос результата выгрузок.
Пользователи (school.user)
import { UserStateKey } from '@exode-team/sdk/api'
// Create — returns the user object (or null)
const user = await exodeApi . school . user . create ({
email: '[email protected] ' ,
profile: { firstName: 'John' , role: 'Student' },
})
// Update — returns the user
await exodeApi . school . user . update ( user . id , { profile: { lastName: 'Smith' } })
// Upsert (create or update by email/phone/tgId/extId)
const { user : u , isCreated } = await exodeApi . school . user . upsert ({
email: '[email protected] ' ,
profile: { firstName: 'John' },
})
// Find — exactly one of login | tgId | extId
const found = await exodeApi . school . user . find ({ login: '[email protected] ' })
// Delete (≤250 at a time) — { deleted, skipped }
const { deleted , skipped } = await exodeApi . school . user . deleteMany ([ 1 , 2 , 3 ], 'Access revocation' )
// State — key from the UserStateKey enum
await exodeApi . school . user . setState ( user . id , UserStateKey . OnBoardingProgress , { step: 2 }) // → boolean
const value = await exodeApi . school . user . getState ( user . id , UserStateKey . OnBoardingProgress ) // → unknown
// Auto-auth token — { session, isCreated }
const { session } = await exodeApi . school . user . createAuthToken ({ userId: user . id })
// session.token → put into ?___uat=<token> for auto-login
Группы (school.group)
// List groups (filter + pagination)
const { items } = await exodeApi . school . group . list ({ courseIds: [ 10 ], take: 50 })
// Add members (≤250) — { exist, created }
await exodeApi . school . group . addMembers ( groupId , [ 1 , 2 , 3 ])
// Remove members (≤250) — { affected }
await exodeApi . school . group . removeMembers ( groupId , [ 1 ])
Курсы (school.course)
// List courses
const { items } = await exodeApi . school . course . list ({ types: [ 'VideoCourse' ], take: 20 })
// Course members' progress (pagination)
const progress = await exodeApi . school . course . progresses ( courseId , { take: 100 })
Доступы к продуктам (school.productAccess)
const { items } = await exodeApi . school . productAccess . list ({ active: true , take: 50 })
Счета (school.invoice)
const { items } = await exodeApi . school . invoice . list ({ types: [ 'Regular' ], take: 50 })
// Form layouts
const layout = await exodeApi . school . form . layoutCreate ({ mode: 'Custom' , name: 'Survey' , internalName: 'survey' })
await exodeApi . school . form . layoutUpdate ( layout . id , { status: 'Published' })
await exodeApi . school . form . layoutDelete ( layout . id ) // → { affected }
// Custom field values
const { items } = await exodeApi . school . form . customFieldValueGet ({ userIds: [ 27 ], take: 50 })
// Set values by slug (type is inferred automatically)
await exodeApi . school . form . customFieldValueSetBySlug ({
userId: 27 ,
layoutId: layout . id ,
values: [{ slug: 'city' , value: 'Tashkent' }, { slug: 'age' , value: 25 }],
})
// Set values by fieldId (typed)
await exodeApi . school . form . customFieldValueSet ({
userId: 27 ,
layoutId: layout . id ,
values: [{ fieldId: 10 , text: 'Tashkent' }],
})
Выгрузки (school.queryExport)
Выгрузки работают асинхронно: генерация запускает workflow, результат опрашивается по UUID.
import { QueryExportType , QueryExportFormat } from '@exode-team/sdk/api'
// 1. Start — returns { uuid, status, ... }
const { uuid } = await exodeApi . school . queryExport . generate ({
type: QueryExportType . GroupMemberFindMany ,
format: QueryExportFormat . Csv ,
variables: { filter: { groupIds: [ 42 ] } },
})
// 2. Poll for result (repeat until status becomes Completed/Failed)
const result = await exodeApi . school . queryExport . getResult ( uuid )
if ( result ?. status === 'Completed' ) {
console . log ( 'File:' , result . result ?. fileUrl )
}
Статусы выполнения
Статус Значение WaitingЗадача в очереди ProcessingВыполняется CompletedГотово, result содержит файл (fileUrl, fileName, fileSize) FailedОшибка CanceledОтменено
generate ограничен лимитом 100 запросов в час . Типы отчётов и переменные — в разделе
Отчёты и выгрузки .
Типизация ответов
Типы всех ответов выведены из тех же серверных zod-схем (через z.infer) и доступны на импорт
(User, Profile, Session, Group, GroupMember, CourseProgress, FormLayout, FormFieldValue,
а также *Output-типы). Рантайм-валидации на стороне клиента нет : контракты уже проверяются на бэкенде
(@ZodResponse), поэтому zod не попадает в рантайм-бандл — ответ возвращается как есть, строго типизированным.
import type { User , ListGroupOutput } from '@exode-team/sdk/api'
Обработка ошибок
Все ошибки оборачиваются в ExodeAPIError:
import { ExodeAPI , ExodeAPIError } from '@exode-team/sdk/api'
try {
await exodeApi . school . user . create ({ email: '[email protected] ' })
} catch ( error ) {
if ( error instanceof ExodeAPIError ) {
console . error ( error . code ) // 400, 401, 403, 408, 429, 0 (сеть)...
console . error ( error . errorCause ) // 'EmailIsBusy', 'Unauthorized', 'Timeout', 'NetworkError'...
console . error ( error . details ) // technical description (optional)
}
}
errorCauseКогда доменные коды (EmailIsBusy, Unauthorized, Forbidden, Rate, …) ответ API с ошибкой Timeoutпревышен timeout (code 408) NetworkErrorсетевая ошибка (code 0) ParseErrorответ не разобрался как JSON
Полный список доменных cause-кодов — в разделе Работа с API .