Skip to main content
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
})

Параметры конфигурации

sellerId
number
required
Идентификатор продавца. Передаётся в заголовке Seller-Id.
schoolId
number
required
Идентификатор школы. Передаётся в заголовке School-Id.
token
string
required
API-токен сервисного пользователя. Передаётся в Authorization: Bearer <token>.
baseUrl
string
Базовый URL API. По умолчанию https://api.exode.biz/saas/v2.
timeout
number
Таймаут запроса в миллисекундах. По умолчанию 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
Подробнее про автологин через ___uat — в разделе Интеграция с Telegram Mini App.

Группы (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 })

Формы (school.form)

// 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.