Для разработчиков

Kotlin

Типизированный клиент для Pachca API на Kotlin. Построен на Ktor с корутинами (suspend), kotlinx.serialization и встроенным retry. Требуется Kotlin 2.2+ и Java 11+.

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

Установка

Пакет публикуется через JitPack. Добавьте его репозиторий в settings.gradle.kts:

dependencyResolutionManagement {    repositories {        mavenCentral()        maven("https://jitpack.io")    }}

И зависимость в build.gradle.kts:

dependencies {    implementation("com.github.pachca:openapi:1.9.0")}

Версия — номер выпуска SDK, все выпуски перечислены в обновлениях.

Создание клиента

Клиенту нужен токен бота или персональный токен. Какой выбрать и кто может его выпустить, описано в разделе Какой токен выбрать.

import com.pachca.sdk.PachcaClient val client = PachcaClient("YOUR_TOKEN")

Первый запрос

// Получение профиляval response = client.profile.getProfile()// → User(id: Int, firstName: String?, lastName: String?, nickname: String, email: String?, phoneNumber: String?, department: String?, title: String?, role: UserRole, suspended: Boolean, inviteStatus: InviteStatus, inviterId: Int?, listTags: List<String>, customProperties: List<CustomProperty(id: Int, name: String, dataType: CustomPropertyDataType, value: String?)>, userStatus: UserStatus(emoji: String, title: String, expiresAt: OffsetDateTime?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Boolean, sso: Boolean, createdAt: OffsetDateTime, lastActivityAt: OffsetDateTime?, timeZone: String?, imageUrl: String?)
Все методы SDK — suspend-функции. Вызывайте их из корутин (runBlocking, launch, async).

Инициализация

import com.pachca.sdk.PachcaClient // Стандартное подключениеval client = PachcaClient("YOUR_TOKEN") // С кастомным базовым URLval client = PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/api/shared/v1")
ПараметрТипПо умолчаниюОписание
tokenString—Bearer-токен для авторизации
baseUrlStringhttps://api.pachca.com/api/shared/v1Базовый URL API

Клиент реализует Closeable — закройте его после использования:

client.use { c ->    val profile = c.profile.getProfile()    println(profile.firstName)} // Или вручнуюclient.close()

Все методы

МетодМетод API
client.oauth.revokeToken()POSTОтзыв токена
client.oauth.exchangeToken()POSTОбмен кода на токен
client.oauth.getTokenInfo()GETИнформация о токене
client.chats.createChat()POSTНовый чат
client.chats.requestExport()POSTЭкспорт сообщений
client.chats.listChats()GETСписок чатов
client.chats.downloadExport()GETСкачать архив экспорта
client.chats.getChat()GETИнформация о чате
client.chats.listCompanyChats()GETСписок чатов пространства
client.chats.updateChat()PUTРедактирование чата
client.chats.archiveChat()PUTАрхивация чата
client.chats.updateChatAvatar()PUTЗагрузка аватара чата
client.chats.unarchiveChat()PUTРазархивация чата
client.chats.markChatUnread()PUTОтметка чата непрочитанным
client.chats.deleteChatAvatar()DELETEУдаление аватара чата
client.profile.getProfile()GETСвой профиль
client.profile.getStatus()GETСвой статус
client.profile.updateProfileAvatar()PUTЗагрузка своего аватара
client.profile.updateStatus()PUTНовый свой статус
client.profile.deleteProfileAvatar()DELETEУдаление своего аватара
client.profile.deleteStatus()DELETEУдаление своего статуса
client.users.createUser()POSTНовый сотрудник
client.users.listUsers()GETСписок сотрудников
client.users.getUser()GETИнформация о сотруднике
client.users.getUserStatus()GETСтатус сотрудника
client.users.updateUser()PUTРедактирование сотрудника
client.users.updateUserAvatar()PUTЗагрузка аватара сотрудника
client.users.updateUserStatus()PUTНовый статус сотрудника
client.users.deleteUser()DELETEУдаление сотрудника
client.users.deleteUserAvatar()DELETEУдаление аватара сотрудника
client.users.deleteUserStatus()DELETEУдаление статуса сотрудника
client.groupTags.createTag()POSTНовый тег
client.groupTags.listTags()GETСписок тегов
client.groupTags.getTag()GETИнформация о теге
client.groupTags.getTagUsers()GETСписок сотрудников тега
client.groupTags.updateTag()PUTРедактирование тега
client.groupTags.deleteTag()DELETEУдаление тега
client.members.addTags()POSTДобавление тегов
client.members.addMembers()POSTДобавление участников
client.members.listMembers()GETСписок участников чата
client.members.updateMemberRole()PUTРедактирование роли участника
client.members.removeTag()DELETEИсключение тега из чата
client.members.leaveChat()DELETEВыход из чата
client.members.removeMember()DELETEИсключение участника
client.threads.createThread()POSTНовый тред
client.threads.createStandaloneThread()POSTНовый самостоятельный тред
client.threads.listThreads()GETСписок тредов
client.threads.getThread()GETИнформация о треде
client.messages.createMessage()POSTНовое сообщение
client.messages.unfurl()POSTUnfurl (разворачивание ссылок)
client.messages.pinMessage()POSTЗакрепление сообщения
client.messages.listChatMessages()GETСписок сообщений чата
client.messages.getMessage()GETИнформация о сообщении
client.messages.updateMessage()PUTРедактирование сообщения
client.messages.deleteMessage()DELETEУдаление сообщения
client.messages.unpinMessage()DELETEОткрепление сообщения
client.readMembers.listReadMembers()GETСписок прочитавших сообщение
client.reactions.addReaction()POSTДобавление реакции
client.reactions.listReactions()GETСписок реакций
client.reactions.removeReaction()DELETEУдаление реакции
client.importantMessages.addMessageToImportant()POSTДобавление сообщения в важные
client.importantMessages.listImportantMessages()GETСписок важных сообщений
client.importantMessages.removeMessageFromImportant()DELETEУдаление сообщения из важных
client.search.searchChats()GETПоиск чатов
client.search.searchMessages()GETПоиск сообщений
client.search.searchUsers()GETПоиск сотрудников
client.tasks.createTask()POSTНовое напоминание
client.tasks.listTasks()GETСписок напоминаний
client.tasks.getTask()GETИнформация о напоминании
client.tasks.updateTask()PUTРедактирование напоминания
client.tasks.deleteTask()DELETEУдаление напоминания
client.drafts.createDraft()POSTНовый черновик
client.drafts.listDrafts()GETСписок черновиков
client.drafts.getDraft()GETИнформация о черновике
client.drafts.updateDraft()PUTРедактирование черновика
client.drafts.deleteDraft()DELETEУдаление черновика
client.views.openView()POSTОткрытие представления
client.views.submitViewResponse()POSTОтвет на отправку формы
client.bots.selfRecreateBotToken()POSTРотация собственного токена бота
client.bots.createBot()POSTНовый бот
client.bots.recreateBotToken()POSTРотация токена бота
client.bots.rotateBotClientSecret()POSTРотация секрета клиента
client.bots.createBotToken()POSTНовый токен бота
client.bots.reissueBotToken()POSTПеревыпуск токена бота
client.bots.listBots()GETСписок ботов
client.bots.getBot()GETИнформация о боте
client.bots.getBotScopes()GETКаталог прав бота
client.bots.listBotTokens()GETСписок токенов бота
client.bots.listCompanyBots()GETСписок ботов пространства
client.bots.getWebhookEvents()GETИстория событий
client.bots.getWebhookEventsStream()GETПоток событий
client.bots.selfUpdateBotWebhook()PUTСаморегистрация вебхука бота
client.bots.updateBot()PUTРедактирование бота
client.bots.updateBotAvatar()PUTЗагрузка аватара бота
client.bots.updateBotToken()PUTИзменение токена бота
client.bots.deleteBot()DELETEУдаление бота
client.bots.deleteBotAvatar()DELETEУдаление аватара бота
client.bots.deleteBotToken()DELETEУдаление токена бота
client.bots.deleteWebhookEvent()DELETEУдаление события истории
client.security.getAuditEvents()GETЖурнал аудита событий
client.customProperties.listProperties()GETСписок дополнительных полей
client.files.uploadFile()POSTЗагрузка файла
client.files.getUploadParams()POSTПолучение подписи, ключа и других параметров
client.files.downloadFile()GETСкачивание файла

Запросы

Все методы — suspend-функции.

GET с параметрами:

import com.pachca.sdk.ChatAvailabilityimport com.pachca.sdk.ChatSortFieldimport com.pachca.sdk.SortOrderimport java.time.OffsetDateTime // Список чатовval lastMessageAtAfter = OffsetDateTime.parse("2025-01-01T00:00:00.000Z")val lastMessageAtBefore = OffsetDateTime.parse("2025-02-01T00:00:00.000Z")val response = client.chats.listChats(sort = ChatSortField.ID, order = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, archived = true, lastMessageAtAfter = lastMessageAtAfter, lastMessageAtBefore = lastMessageAtBefore, personal = false, limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")// → ListChatsResponse(data: List<Chat>, meta: PaginationMeta)

POST с телом запроса:

import com.pachca.sdk.ChatCreateRequestimport com.pachca.sdk.ChatCreateRequestChat // Создание чатаval request = ChatCreateRequest(    chat = ChatCreateRequestChat(        name = "🤿 aqua",        memberIds = listOf(123),        groupTagIds = listOf(123),        channel = true,        public = false    ))val response = client.chats.createChat(request = request)// → CreateChatResponse(data: Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List<Int>, groupTagIds: List<Int>, channel: Boolean, archived: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String, avatarUrl: String?), meta: CreateChatResponseMeta(notFoundMemberIds: List<Int>, notFoundGroupTagIds: List<Int>))

Простой вызов по ID:

// Получение чатаval response = client.chats.getChat(id = 334)// → Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List<Int>, groupTagIds: List<Int>, channel: Boolean, archived: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String, avatarUrl: String?)

Значение null

Необязательное поле, которое не задано, в запрос не попадает, поэтому метод обновления меняет только то, что вы передали. Некоторые поля API принимают null: например, null в oauth_client выключает у бота авторизацию от имени сотрудника, а в webhook.kind удаляет входящий вебхук. Чтобы отправить null, назовите поле в nullFields той же модели запроса так, как оно называется в Kotlin:

val request = BotUpdateRequest(    webhook = BotUpdateRequestWebhook(nullFields = setOf("kind")),)

nullFields есть у моделей запроса с такими полями, а в его описании перечислены имена, которые в нём можно указать. С другим именем SDK выбрасывает SerializationException и запрос не отправляет.

Пагинация

Поля пагинации называются как в API, но в camelCase: meta.paginate.nextPage, prevPage, hasNext и hasPrev, а у методов поиска ещё meta.total. Какие поля приходят у каждого метода и как понять, что страницы закончились, описано в разделе Пагинация.

Ручная пагинация

var cursor: String? = nullvar hasNext = truewhile (hasNext) {    val response = client.chats.listChats(limit = 50, cursor = cursor)    if (response.data.isEmpty()) break    for (chat in response.data) {        println("${chat.id} ${chat.name}")    }    cursor = response.meta.paginate.nextPage    hasNext = response.meta.paginate.hasNext ?: true}

В модели поле hasNext имеет тип Boolean?. Если оно null, цикл идёт дальше и останавливается на пустой странице. Так же обходят страницы методы *All().

Автопагинация

Для каждого метода с пагинацией есть *All() вариант:

// Все чаты одним спискомval chats = client.chats.listChatsAll()println("Всего: ${chats.size}")

Доступные методы автопагинации:

МетодВозвращает
security.getAuditEventsAll()List<AuditEvent>
bots.listBotsAll()List<BotResponse>
bots.listBotTokensAll()List<BotAccessToken>
bots.listCompanyBotsAll()List<CompanyBotResponse>
bots.getWebhookEventsAll()List<WebhookEvent>
chats.listChatsAll()List<Chat>
chats.listCompanyChatsAll()List<Chat>
drafts.listDraftsAll()List<Draft>
groupTags.listTagsAll()List<GroupTag>
groupTags.getTagUsersAll()List<User>
importantMessages.listImportantMessagesAll()List<Message>
members.listMembersAll()List<User>
messages.listChatMessagesAll()List<Message>
reactions.listReactionsAll()List<Reaction>
search.searchChatsAll()List<Chat>
search.searchMessagesAll()List<Message>
search.searchUsersAll()List<User>
tasks.listTasksAll()List<Task>
threads.listThreadsAll()List<Thread>
users.listUsersAll()List<User>

Обработка ошибок

SDK выбрасывает два типа исключений:

ApiError

Возникает при ошибках 400, 403, 404, 409, 410, 422 с массивом errors в теле:

import com.pachca.sdk.ApiError try {    client.chats.createChat(request)} catch (error: ApiError) {    for (e in error.errors) {        println("${e.key}: ${e.message}")  // name: не может быть пустым        println(e.code)                    // blank    }}

Поля ApiErrorItem:

ПолеТипОписание
keyStringПоле, вызвавшее ошибку
valueJsonElement?Переданное значение
messageStringТекст ошибки
codeValidationErrorCodeКод валидации
payloadMap<String, JsonElement>?Дополнительные данные

Код статуса ответа — в поле statusCode. Если ответ с ошибкой пришёл без тела или не в JSON, SDK тоже выбрасывает ApiError: с кодом статуса и пустым errors. Так бывает с ответом 404 без тела от метода GETПоток событий, когда история событий выключена, и со страницей прокси при 502.

OAuthError

Возникает при ошибке авторизации: 401, а также 403 с кодом insufficient_scope или role_forbidden и отказе при обмене кода на токен. Код — в поле error, список кодов — в разделе Ошибки авторизации:

import com.pachca.sdk.OAuthError try {    client.profile.getProfile()} catch (error: OAuthError) {    println(error.error)              // "insufficient_scope"    println(error.errorDescription)   // описание ошибки}

Поле error — перечисление OAuthErrorCode: его сравнивают с константой, например error.error == OAuthErrorCode.INSUFFICIENT_SCOPE, а строку кода даёт error.error.value.

Повторные запросы

Паузы между попытками, к каждой SDK добавляет случайную величину до 25%:

  • 429 с заголовком Retry-After: срок из заголовка
  • 429 без заголовка: 1 сек, 2 сек, 4 сек
  • 5xx: ~10 сек, ~20 сек, ~40 сек
  • Сетевая ошибка у GET, PUT и DELETE: ~10 сек, ~20 сек, ~40 сек

Таймаут запроса и сетевую ошибку у POST SDK не повторяет. Повторы делает плагин Ktor HttpRequestRetry.

Общие правила повторов — в разделе Повторные запросы.

Типы

Все типы — @Serializable data class'ы:

import com.pachca.sdk.* // Моделиval chat: Chatval message: Messageval user: User // Запросыval req: ChatCreateRequest // Перечисленияval key: AuditEventKey = AuditEventKey.USER_LOGINval availability: ChatAvailability = ChatAvailability.PUBLICval role: ChatMemberRole = ChatMemberRole.ADMINval status: TaskStatus = TaskStatus.DONE // Ошибкиval apiError: ApiErrorval oauthError: OAuthError

Перечисление в ответе — value class со строкой value: значение, которого SDK ещё не знает, приходит как есть, и isKnown у него false. Поэтому when по такому перечислению заканчивается веткой else. Строку значения возвращает и toString(), а имя константы даёт name.

Все перечисления SDK: AuditEventKey, AuditLoginGrantDenyReason, AuditLoginGrantDevicePlatform, BotCanEdit, BotEventName, BotTemplateEngine, BotTriggerOn, BotWebhookKind, BotWhoCanAdd, ChatActivity, ChatAvailability, ChatMemberRole, ChatMemberRoleFilter, ChatSortField, ChatSubtype, CustomPropertyDataType, DraftRepetitionInterval, DraftType, FileTarget, FileType, InviteStatus, MemberEventType, MessageEntityType, MessageSearchSort, MessageSortField, OAuthErrorCode, OAuthScope, OAuthTokenTypeHint, ReactionEventType, SearchEntityType, SearchSortOrder, SortOrder, TaskKind, TaskStatus, UserCreateRole, UserEventType, UserRole, UserRoleInput, ValidationErrorCode, VideoCallEventType, VoiceTranscriptStatus и WebhookEventType.

Тело вебхука

Тела событий вебхука описаны классами MessageWebhookPayload, ReactionWebhookPayload, ButtonWebhookPayload, ViewSubmitWebhookPayload, ChatMemberWebhookPayload, CompanyMemberWebhookPayload, LinkSharedWebhookPayload и VideoCallWebhookPayload. Их общий интерфейс WebhookPayloadUnion разбирается своим сериализатором: класс он выбирает по полям type и event. Разбирает тело Json из kotlinx.serialization, эта библиотека приходит в проект вместе с SDK:

import com.pachca.sdk.ButtonWebhookPayloadimport com.pachca.sdk.WebhookPayloadUnionimport kotlinx.serialization.json.Json val json = Json { ignoreUnknownKeys = true } // body — тело запроса строкойwhen (val payload = json.decodeFromString<WebhookPayloadUnion>(body)) {    is ButtonWebhookPayload -> println("${payload.messageId} ${payload.data}")    else -> {}}

Подпись проверяйте по исходному телу до разбора, как в разделе Безопасность. Те же классы отдают функции pollWebhookEvents и pollWebhookPayloads, которые забирают события из истории: пример — в разделе Пример поллинга.

Зависимости

ПакетВерсияНазначение
kotlinx-serialization-json1.9.0JSON-сериализация
kotlinx-coroutines-core1.10.2Корутины
ktor-client-core3.2.3HTTP-клиент
ktor-client-cio3.2.3CIO-движок
ktor-client-auth3.2.3Bearer-авторизация
ktor-client-content-negotiation3.2.3Content negotiation
ktor-serialization-kotlinx-json3.2.3JSON для Ktor

Первые три пакета приходят в проект вместе с SDK и доступны в вашем коде: их типы есть в API SDK (JsonElement, Flow и HttpClient), поэтому отдельно их подключать не нужно. Остальные SDK использует внутри.

Примеры

Отправка сообщения:

import com.pachca.sdk.MessageCreateRequestimport com.pachca.sdk.MessageCreateRequestMessageimport com.pachca.sdk.PachcaClient val client = PachcaClient("YOUR_TOKEN") val request = MessageCreateRequest(message = MessageCreateRequestMessage(entityId = 334, content = "Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)"))val response = client.messages.createMessage(request = request)// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: OffsetDateTime, url: String, files: List<File(id: Int, key: String, name: String, fileType: FileType, url: String, width: Int?, height: Int?, durationMs: Int?)>, voiceContent: VoiceContent(durationMs: Int, waveform: String, transcript: String?, transcriptStatus: VoiceTranscriptStatus?)?, buttons: List<List<Button(text: String, url: String?, data: String?)>>?, thread: MessageThread(id: Long, chatId: Long)?, forwarding: Forwarding(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: OffsetDateTime, originalThreadId: Int?, originalThreadMessageId: Int?, originalThreadParentChatId: Int?)?, parentMessageId: Int?, displayAvatarUrl: String?, displayName: String?, changedAt: OffsetDateTime?, deletedAt: OffsetDateTime?, important: Boolean)

Список сотрудников:

import com.pachca.sdk.PachcaClient val client = PachcaClient("YOUR_TOKEN") val response = client.users.listUsers(query = "Олег", limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")// → ListUsersResponse(data: List<User>, meta: PaginationMeta)

Создание напоминания:

import com.pachca.sdk.PachcaClientimport com.pachca.sdk.TaskCreateRequestimport com.pachca.sdk.TaskCreateRequestCustomPropertyimport com.pachca.sdk.TaskCreateRequestTaskimport com.pachca.sdk.TaskKindimport java.time.OffsetDateTime val client = PachcaClient("YOUR_TOKEN") val request = TaskCreateRequest(    task = TaskCreateRequestTask(        kind = TaskKind.REMINDER,        content = "Забрать со склада 21 заказ",        dueAt = OffsetDateTime.parse("2020-06-05T12:00:00.000+03:00"),        priority = 2,        performerIds = listOf(123),        chatId = 456,        allDay = false,        customProperties = listOf(TaskCreateRequestCustomProperty(id = 78, value = "Синий склад"))    ))val response = client.tasks.createTask(request = request)// → Task(id: Int, kind: TaskKind, content: String, dueAt: OffsetDateTime?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: OffsetDateTime, performerIds: List<Int>, allDay: Boolean, customProperties: List<CustomProperty(id: Int, name: String, dataType: CustomPropertyDataType, value: String?)>)