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

Kotlin

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

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

Установка

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

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

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

Получите 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, 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")
ПараметрТипПо умолчаниюОписание
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.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.updateChat()PUTРедактирование чата
client.chats.archiveChat()PUTАрхивация чата
client.chats.unarchiveChat()PUTРазархивация чата
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.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.selfRecreateBotToken()POSTРотация собственного токена бота
client.bots.createBot()POSTНовый бот
client.bots.recreateBotToken()POSTРотация токена бота
client.bots.listBots()GETСписок ботов
client.bots.getBot()GETИнформация о боте
client.bots.getWebhookEvents()GETИстория событий
client.bots.selfUpdateBotWebhook()PUTСаморегистрация вебхука бота
client.bots.updateBot()PUTРедактирование бота
client.bots.deleteBot()DELETEУдаление бота
client.bots.deleteWebhookEvent()DELETEУдаление события
client.security.getAuditEvents()GETЖурнал аудита событий
client.customProperties.listProperties()GETСписок дополнительных полей
client.files.uploadFile()POSTЗагрузка файла
client.files.getUploadParams()POSTПолучение подписи, ключа и других параметров

Запросы

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

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

import com.pachca.sdk.ChatAvailabilityimport com.pachca.sdk.ChatSortFieldimport com.pachca.sdk.SortOrder // Список чатов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)// → 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)

Простой вызов по 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)

Пагинация

SDK работает с двумя группами методов, возвращающих списки, у которых разная структура meta — это важно учитывать при ручной пагинации:

  • Списочные методы (client.users.listUsers(), client.chats.listChats(), client.messages.listChatMessages() и т.д.) — meta.paginate с полями nextPage, prevPage, hasNext, hasPrev. Признак конца — hasNext == false. Курсор prevPage нужен для polling новых записей «сверху» списка.
  • Методы поиска (client.search.searchUsers(), client.search.searchChats(), client.search.searchMessages()) — meta с полями total и paginate.nextPage (без prevPage/hasNext/hasPrev). Признак конца — пустой data или совпадение числа полученных записей с total.

Курсоры — непрозрачные токены, никогда не бывают null/пустыми. Подробное описание полей и примеры — на странице Пагинация.

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

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

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

Для каждого метода с пагинацией есть *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 (500, 502, 503, 504):

  • До 3 повторов на каждый запрос
  • 429: если сервер вернул заголовок Retry-After — ждёт указанное время, иначе — экспоненциальный backoff с jitter
  • 5xx: экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
  • Реализовано через плагин Ktor HttpRequestRetry
  • Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов

Типы

Все типы — @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,            durationMs = 5400,            waveform = "4,8,12,20,16,10,6,3"        )),        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: OffsetDateTime, url: String, files: List<File(id: Int, key: String, name: String, fileType: FileType, url: String, width: Int?, height: Int?)>, voiceContent: VoiceContent(durationMs: Int, waveform: String, transcript: String?)?, 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?) // Список сотрудников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 = 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?)>)