Kotlin

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

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

Установка

Добавьте зависимость в build.gradle.kts:

dependencies {    implementation("com.pachca:pachca-sdk:1.0.1")}

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

Получите API-токен в интерфейсе Пачки: Настройки → Автоматизации → API (подробнее — Авторизация).

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, listTags: List<String>, customProperties: List<CustomProperty(id: Int, name: String, dataType: CustomPropertyDataType, value: String)>, userStatus: UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Boolean, sso: Boolean, createdAt: String, lastActivityAt: String, 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")
ПараметрТипПо умолчаниюОписание
tokenStringBearer-токен для авторизации
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.common.requestExport()POSTЭкспорт сообщений
client.common.uploadFile()POSTЗагрузка файла
client.common.getUploadParams()POSTПолучение подписи, ключа и других параметров
client.common.downloadExport()GETСкачать архив экспорта
client.common.listProperties()GETСписок дополнительных полей
client.profile.getTokenInfo()GETИнформация о токене
client.profile.getProfile()GETИнформация о профиле
client.profile.getStatus()GETТекущий статус
client.profile.updateStatus()PUTНовый статус
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.updateUserStatus()PUTНовый статус сотрудника
client.users.deleteUser()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.chats.createChat()POSTНовый чат
client.chats.listChats()GETСписок чатов
client.chats.getChat()GETИнформация о чате
client.chats.updateChat()PUTОбновление чата
client.chats.archiveChat()PUTАрхивация чата
client.chats.unarchiveChat()PUTРазархивация чата
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.getThread()GETИнформация о треде
client.messages.createMessage()POSTНовое сообщение
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.linkPreviews.createLinkPreviews()POSTUnfurl (разворачивание ссылок)
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.views.openView()POSTОткрытие представления
client.bots.getWebhookEvents()GETИстория событий
client.bots.updateBot()PUTРедактирование бота
client.bots.deleteWebhookEvent()DELETEУдаление события
client.security.getAuditEvents()GETЖурнал аудита событий

Запросы

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

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

import com.pachca.sdk.ChatAvailabilityimport com.pachca.sdk.SortOrder // Список чатовval response = client.chats.listChats(sortId = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, lastMessageAtAfter = "2025-01-01T00:00:00.000Z", lastMessageAtBefore = "2025-02-01T00:00:00.000Z", 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)// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List<Int>, groupTagIds: List<Int>, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)

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

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

Пагинация

Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит meta?.paginate?.nextPage — курсор для следующей страницы.

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

var cursor: String? = nulldo {    val response = client.users.listUsers(limit = 50, cursor = cursor)    for (user in response.data) {        println("${user.firstName} ${user.lastName}")    }    cursor = response.meta?.paginate?.nextPage} while (cursor != null)

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

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

// Все пользователи одним спискомval users = client.users.listUsersAll()println("Всего: ${users.size}")

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

МетодВозвращает
security.getAuditEventsAll()List<AuditEvent>
bots.getWebhookEventsAll()List<WebhookEvent>
chats.listChatsAll()List<Chat>
groupTags.listTagsAll()List<GroupTag>
groupTags.getTagUsersAll()List<User>
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>
users.listUsersAll()List<User>

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

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

ApiError

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

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)                    // ValidationErrorCode.BLANK    }}

Поля ApiErrorItem:

ПолеТипОписание
keyStringПоле, вызвавшее ошибку
valueString?Переданное значение
messageStringТекст ошибки
codeValidationErrorCodeКод валидации
payloadString?Дополнительные данные

OAuthError

Возникает при ошибке авторизации (401):

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

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

SDK автоматически повторяет запрос при получении 429 Too Many Requests или серверной ошибки (5xx):

  • До 3 повторов на каждый запрос
  • Если сервер вернул заголовок Retry-After — ждёт указанное время
  • Иначе — линейный backoff: 1 сек, 2 сек, 3 сек
  • Реализовано через плагин 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.IS_OPENval role: ChatMemberRole = ChatMemberRole.ADMINval status: TaskStatus = TaskStatus.DONE // Ошибкиval apiError: ApiErrorval oauthError: OAuthError

Доступные перечисления: AuditEventKey, ChatAvailability, ChatMemberRole, ChatMemberRoleFilter, ChatSubtype, CustomPropertyDataType, FileType, InviteStatus, MemberEventType, MessageEntityType, OAuthScope, ReactionEventType, SearchEntityType, SearchSortOrder, SortOrder, TaskKind, TaskStatus, UserEventType, UserRole, ValidationErrorCode, WebhookEventType.

Зависимости

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

Примеры

import com.pachca.sdk.Buttonimport com.pachca.sdk.FileTypeimport com.pachca.sdk.MessageCreateRequestimport com.pachca.sdk.MessageCreateRequestFileimport com.pachca.sdk.MessageCreateRequestMessageimport com.pachca.sdk.MessageEntityTypeimport com.pachca.sdk.PachcaClientimport com.pachca.sdk.TaskCreateRequestimport com.pachca.sdk.TaskCreateRequestCustomPropertyimport com.pachca.sdk.TaskCreateRequestTaskimport com.pachca.sdk.TaskKind val client = PachcaClient("YOUR_TOKEN") // Отправка сообщенияval request = MessageCreateRequest(    message = MessageCreateRequestMessage(        entityType = MessageEntityType.DISCUSSION,        entityId = 334,        content = "Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)",        files = listOf(MessageCreateRequestFile(            key = "attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png",            name = "logo.png",            fileType = FileType.IMAGE,            size = 12345,            width = 800,            height = 600        )),        buttons = listOf(listOf(Button(            text = "Подробнее",            url = "https://example.com/details",            data = "awesome"        ))),        parentMessageId = 194270,        displayAvatarUrl = "https://example.com/avatar.png",        displayName = "Бот Поддержки",        skipInviteMentions = false,        linkPreview = false    ))val response = client.messages.createMessage(request = request)// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: String, url: String, files: List<File(id: Int, key: String, name: String, fileType: FileType, url: String, width: Int?, height: Int?)>, buttons: List<List<Button(text: String, url: String?, data: String?)>>?, thread: Thread(id: Long, chatId: Long, messageId: Long, messageChatId: Long, updatedAt: String)?, forwarding: Forwarding(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: String, originalThreadId: Int?, originalThreadMessageId: Int?, originalThreadParentChatId: Int?)?, parentMessageId: Int?, displayAvatarUrl: String?, displayName: String?, changedAt: String?, deletedAt: String?) // Список сотрудниковval response = client.users.listUsers(query = "Олег", limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")// → ListUsersResponse(data: List<User>, meta: PaginationMeta?) // Создание задачиval request = TaskCreateRequest(    task = TaskCreateRequestTask(        kind = TaskKind.REMINDER,        content = "Забрать со склада 21 заказ",        dueAt = "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: String?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: String, performerIds: List<Int>, allDay: Boolean, customProperties: List<CustomProperty(id: Int, name: String, dataType: CustomPropertyDataType, value: String)>)