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

Swift

Типизированный клиент для Pachca API на Swift. Построен на URLSession с async throws, Codable-моделями и встроенным retry. Требуется Swift 5.9+, macOS 13+ или iOS 16+.

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

Установка

Добавьте пакет в Package.swift:

dependencies: [    .package(url: "https://github.com/pachca/openapi", from: "1.0.0")]

Или через Xcode: File > Add Package Dependencies > https://github.com/pachca/openapi.

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

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

import PachcaSDK let client = PachcaClient(token: "YOUR_TOKEN")

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

import PachcaSDK // Получение профиляlet response = try await client.profile.getProfile()// → User(id: Int, firstName: String?, lastName: String?, nickname: String, email: String?, phoneNumber: String?, department: String?, title: String?, role: UserRole, suspended: Bool, inviteStatus: InviteStatus, inviterId: Int?, listTags: [String], customProperties: [CustomProperty(id: Int, name: String, dataType: CustomPropertyDataType, value: String?)], userStatus: UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Bool, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Bool, sso: Bool, createdAt: String, lastActivityAt: String?, timeZone: String?, imageUrl: String?)

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

import PachcaSDK // Стандартное подключениеlet client = PachcaClient(token: "YOUR_TOKEN") // С кастомным базовым URLlet client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.example.com/api/shared/v1")
ПараметрТипПо умолчаниюОписание
tokenStringBearer-токен для авторизации
baseURLStringhttps://api.pachca.com/api/shared/v1Базовый URL API

Клиент — value type (struct), использует URLSession.shared. Явного закрытия не требует.

Все методы

МетодМетод 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Получение подписи, ключа и других параметров

Запросы

Все методы — async throws.

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

import PachcaSDK // Список чатовlet response = try await client.chats.listChats(sort: .id, order: .desc, availability: .isMember, archived: true, lastMessageAtAfter: "2025-01-01T00:00:00.000Z", lastMessageAtBefore: "2025-02-01T00:00:00.000Z", personal: false, limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")// → ListChatsResponse(data: [Chat], meta: PaginationMeta)

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

import PachcaSDK // Создание чатаlet body = ChatCreateRequest(    chat: ChatCreateRequestChat(        name: "🤿 aqua",        memberIds: [123],        groupTagIds: [123],        channel: true,        `public`: false    ))let response = try await client.chats.createChat(body: body)// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: [Int], groupTagIds: [Int], channel: Bool, archived: Bool, personal: Bool, `public`: Bool, lastMessageAt: String, meetRoomUrl: String)

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

import PachcaSDK // Получение чатаlet response = try await client.chats.getChat(id: 334)// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: [Int], groupTagIds: [Int], channel: Bool, archived: Bool, personal: Bool, `public`: Bool, lastMessageAt: String, 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.

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

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

var cursor: String? = nilvar hasNext = truewhile hasNext {    let response = try await client.users.listUsers(limit: 50, cursor: cursor)    for user in response.data {        print("\(user.firstName) \(user.lastName)")    }    cursor = response.meta.paginate.nextPage    hasNext = response.meta.paginate.hasNext}

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

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

// Все пользователи одним массивомlet users = try await client.users.listUsersAll()print("Всего: \(users.count)")

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

МетодВозвращает
security.getAuditEventsAll()[AuditEvent]
bots.getWebhookEventsAll()[WebhookEvent]
chats.listChatsAll()[Chat]
groupTags.listTagsAll()[GroupTag]
groupTags.getTagUsersAll()[User]
members.listMembersAll()[User]
messages.listChatMessagesAll()[Message]
reactions.listReactionsAll()[Reaction]
search.searchChatsAll()[Chat]
search.searchMessagesAll()[Message]
search.searchUsersAll()[User]
tasks.listTasksAll()[Task]
users.listUsersAll()[User]

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

SDK выбрасывает два типа ошибок (реализуют протокол Error):

ApiError

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

do {    try await client.chats.createChat(request)} catch let error as ApiError {    for e in error.errors {        print("\(e.key): \(e.message)")  // "name: не может быть пустым"        print(e.code)                    // .blank    }}

Поля ApiErrorItem:

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

OAuthError

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

do {    try await client.profile.getProfile()} catch let error as OAuthError {    print(error.error)              // "Token not found"    print(error.errorDescription)   // описание ошибки}

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

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

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

Типы

Все типы — Codable struct'ы:

import PachcaSDK // Моделиlet chat: Chatlet message: Messagelet user: User // Перечисленияlet key: AuditEventKey = .userLoginlet availability: ChatAvailability = .isOpenlet role: ChatMemberRole = .adminlet status: TaskStatus = .done // Ошибкиlet apiError: ApiErrorlet oauthError: OAuthError

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

Платформы

ПлатформаМинимальная версия
macOS13.0+
iOS16.0+
Swift5.9+

Примеры

import PachcaSDK let client = PachcaClient(token: "YOUR_TOKEN") // Отправка сообщенияlet body = MessageCreateRequest(    message: MessageCreateRequestMessage(        entityType: .discussion,        entityId: 334,        content: "Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)",        files: [MessageCreateRequestFile(            key: "attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png",            name: "logo.png",            fileType: .image,            size: 12345,            width: 800,            height: 600,            durationMs: 5400,            waveform: "4,8,12,20,16,10,6,3"        )],        buttons: [[Button(            text: "Подробнее",            url: "https://example.com/details",            data: "awesome"        )]],        parentMessageId: 194270,        displayAvatarUrl: "https://example.com/avatar.png",        displayName: "Бот Поддержки",        skipInviteMentions: false    ),    linkPreview: false)let response = try await client.messages.createMessage(body: body)// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: String, url: String, files: [File(id: Int, key: String, name: String, fileType: FileType, url: String, width: Int?, height: Int?)], voiceContent: VoiceContent(durationMs: Int, waveform: String, transcript: String?)?, buttons: [[Button(text: String, url: String?, data: String?)]]?, thread: MessageThread(id: Int64, chatId: Int64)?, 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?) // Список сотрудниковlet response = try await client.users.listUsers(query: "Олег", limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")// → ListUsersResponse(data: [User], meta: PaginationMeta) // Создание задачиlet body = TaskCreateRequest(    task: TaskCreateRequestTask(        kind: .reminder,        content: "Забрать со склада 21 заказ",        dueAt: "2020-06-05T12:00:00.000+03:00",        priority: 2,        performerIds: [123],        chatId: 456,        allDay: false,        customProperties: [TaskCreateRequestCustomProperty(id: 78, value: "Синий склад")]    ))let response = try await client.tasks.createTask(body: body)// → Task(id: Int, kind: TaskKind, content: String, dueAt: String?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: String, performerIds: [Int], allDay: Bool, customProperties: [CustomProperty(id: Int, name: String, dataType: CustomPropertyDataType, value: String?)])