# Pachca API — Complete English Documentation > REST API for Pachca corporate messenger. Manage messages, chats, users, tasks, bots, and webhooks. > Base URL: https://api.pachca.com/api/shared/v1 # LIBRARY RULES ## Authentication - All requests require Bearer token in Authorization header: `Authorization: Bearer ` - Token types: **admin** (full access — manage users, tags, delete messages), **bot** (send messages with custom name/avatar, receive webhooks), **user** (limited access) - Get admin token: Settings → Automations → API. Get bot token: per-bot in Settings → Automations → Integrations - Tokens are long-lived and do not expire. Can be reset by admin in Settings - TypeScript SDK: `const client = new PachcaClient("YOUR_TOKEN")` - Python SDK: `client = PachcaClient("YOUR_TOKEN")` ## Pagination - Cursor-based: use `limit` (1–50, default 50) and `cursor` query parameters - Response includes `meta.paginate.next_page` — cursor for next page - End of data: when `data` array is empty (cursor is never null) - TypeScript auto-pagination: `client.users.listUsersAll()` returns flat array of all results - Python auto-pagination: `await client.users.list_users_all()` returns list of all results - Available for: users, chats, messages, members, tags, reactions, tasks, search results, audit events, webhook events ## Rate Limiting - Messages (POST/PUT/DELETE /messages): ~4 req/sec per chat (burst: 30/sec for 5s) - Message read (GET /messages): ~10 req/sec - Other endpoints: ~50 req/sec - On `429 Too Many Requests`: respect `Retry-After` header value (seconds) - Recommended retry strategy: exponential backoff with jitter — base delay × 2^attempt × random(0.5–1.5) - SDK (@pachca/sdk, pachca-sdk) handles retry automatically: 3 retries, respects Retry-After, exponential backoff for 5xx ## Webhooks (Real-time Events) - Create a bot in Pachca: Automations → Integrations → Bots - Set webhook URL in bot settings → Outgoing Webhook tab - Events: `new_message`, `edit_message`, `delete_message`, `new_reaction`, `delete_reaction`, `button_pressed`, `view_submit`, `chat_member_changed`, `company_member_changed`, `link_shared` - Verify: HMAC-SHA256 of raw body with bot's Signing Secret - Header: `Pachca-Signature` contains hex digest - Replay protection: check `webhook_timestamp` within ±60 seconds of current time - Alternative to webhooks: polling via GET /webhooks/events (enable "Save event history" in bot settings) - IP whitelist: Pachca webhook IP is `37.200.70.177` ## File Uploads (3-step process) - Step 1: POST /uploads → get S3 presigned params (`direct_url`, `key`, `policy`, `x-amz-signature`, etc.) - Step 2: POST to `direct_url` (external S3 URL, NOT a Pachca API endpoint) with multipart/form-data — all params + `file` as LAST field - Step 3: Replace `${filename}` in `key` with actual filename, include in message `files` array - Note: `direct_url` is an external S3 presigned URL and does not require Authorization header ## User Status - Get any user's status: GET /users/{id}/status → `{ emoji, title, expires_at, is_away, away_message }` - Set own status: PUT /profile/status `{ status: { emoji, title, expires_at, is_away, away_message } }` - Clear own status: DELETE /profile/status - Admin can manage any user: PUT /users/{id}/status, DELETE /users/{id}/status - No real-time presence webhooks — use polling with ≥60s interval for monitoring status changes ## Error Handling - `400`: validation errors — `{ errors: [{ key, value, message, code }] }` with codes: `blank`, `invalid`, `taken`, `too_short`, `too_long`, `not_a_number` - `401`: unauthorized — `{ error, error_description }` (OAuthError) - `403`: forbidden — insufficient permissions. May return ApiError (business logic) or OAuthError (`insufficient_scope`) - `404`: not found - `409`: conflict (duplicate) - `422`: unprocessable — `{ errors: [{ key, value, message, code }] }` - `429`: rate limited — respect `Retry-After` header, use exponential backoff with jitter - SDK auto-retries `429` and `5xx` errors (3 attempts with exponential backoff) ## Idempotency and Reliability - Pachca API operations are NOT idempotent by default — duplicate POST requests create duplicate resources - Client-side deduplication: track request IDs, check before sending, store results with TTL - Webhooks use at-least-once delivery — handlers MUST be idempotent (dedup by event fields: id + type + event) - For multi-step operations: implement compensating actions (saga pattern) for failure recovery - Separate critical operations (create chat) from non-critical (send welcome message) with independent error handling ## SDK (Typed Clients for 6 Languages) - Unified pattern: `PachcaClient(token)` → `client.service.method(request)` - **Input**: path params and body fields (≤2) expand to method arguments; otherwise a single request object - **Output**: if API response has a single `data` field, SDK returns its contents directly - Service, method, and field names match operationId and parameters from OpenAPI | Language | Package | Install | |----------|---------|---------| | TypeScript | `@pachca/sdk` | `npm install @pachca/sdk` | | Python | `pachca-sdk` | `pip install pachca-sdk` | | Go | `github.com/pachca/go-sdk` | `go get github.com/pachca/openapi/sdk/go/generated` | | Kotlin | `com.pachca:sdk` | `implementation("com.pachca:pachca-sdk:1.0.1")` | | Swift | `PachcaSDK` | SPM: `https://github.com/pachca/openapi` | | C# | `Pachca.Sdk` | `dotnet add package Pachca.Sdk` | --- # How-to Guides ## How to authenticate with the API All API requests require a Bearer token in the Authorization header. ### curl ```bash curl -H "Authorization: Bearer YOUR_TOKEN" https://api.pachca.com/api/shared/v1/users ``` ### TypeScript SDK ```typescript import { PachcaClient } from "@pachca/sdk" const client = new PachcaClient("YOUR_TOKEN") const profile = await client.profile.getProfile() console.log(profile.id, profile.firstName) ``` ### Python SDK ```python from pachca.client import PachcaClient client = PachcaClient("YOUR_TOKEN") profile = await client.profile.get_profile() print(profile.id, profile.first_name) ``` Token types: **admin** (full access, get in Settings → Automations → API), **bot** (messaging + webhooks, per-bot in Integrations), **user** (limited). ## How to paginate through results ### TypeScript SDK — auto-pagination ```typescript // Returns all results as a flat array (handles cursors automatically) const allUsers = await client.users.listUsersAll() console.log(`Total: ${allUsers.length}`) // Manual pagination with cursor control let cursor: string | undefined for (;;) { const response = await client.users.listUsers({ limit: 50, cursor }) if (response.data.length === 0) break for (const user of response.data) { console.log(user.firstName, user.lastName) } cursor = response.meta.paginate.nextPage } ``` ### Python SDK — auto-pagination ```python # Returns all results as a list (handles cursors automatically) all_users = await client.users.list_users_all() print(f"Total: {len(all_users)}") # Manual pagination with cursor control from pachca.models import ListUsersParams cursor = None while True: response = await client.users.list_users(ListUsersParams(limit=50, cursor=cursor)) if not response.data: break for user in response.data: print(user.first_name, user.last_name) cursor = response.meta.paginate.next_page ``` Auto-pagination methods: `listUsersAll()`, `listChatsAll()`, `listChatMessagesAll()`, `listMembersAll()`, `listTagsAll()`, `listTasksAll()`, `searchMessagesAll()`, `searchChatsAll()`, `searchUsersAll()`, `listReactionsAll()`, `getAuditEventsAll()`, `getWebhookEventsAll()`. ## How to create chats and manage members ### TypeScript SDK ```typescript import { PachcaClient, MessageEntityType } from "@pachca/sdk" const client = new PachcaClient("YOUR_TOKEN") // Create a group chat with initial members const chat = await client.chats.createChat({ chat: { name: "Project Discussion", memberIds: [1, 2, 3], channel: false, public: false } }) console.log("Created chat:", chat.id, chat.name) // Add more members later await client.members.addMembers(chat.id, { memberIds: [4, 5] }) // Remove a member await client.members.removeMember(chat.id, 5) // List current members const members = await client.members.listMembersAll(chat.id) console.log("Members:", members.map(m => m.firstName)) // Send a message to the chat await client.messages.createMessage({ message: { entityType: MessageEntityType.Discussion, entityId: chat.id, content: "Welcome everyone!" } }) ``` ### Python SDK ```python from pachca.client import PachcaClient from pachca.models import ChatCreateRequest, ChatCreateRequestChat, MessageCreateRequest, MessageCreateRequestMessage, AddMembersRequest client = PachcaClient("YOUR_TOKEN") # Create a group chat with initial members chat = await client.chats.create_chat(ChatCreateRequest( chat=ChatCreateRequestChat(name="Project Discussion", member_ids=[1, 2, 3], channel=False, public=False) )) print("Created chat:", chat.id, chat.name) # Add more members later await client.members.add_members(chat.id, AddMembersRequest(member_ids=[4, 5])) # List current members members = await client.members.list_members_all(chat.id) # Send a message to the chat await client.messages.create_message(MessageCreateRequest( message=MessageCreateRequestMessage(entity_type="discussion", entity_id=chat.id, content="Welcome everyone!") )) ``` Chat types: `channel: true` creates a channel (one-way announcements), `channel: false` creates a group chat (everyone can write). `public: true` makes it visible to all workspace members. ## How to set up webhooks for real-time updates ### Step-by-step setup 1. Create a bot in Pachca: **Automations** → **Integrations** → **Bots** 2. In bot settings, go to **Outgoing Webhook** tab and set your HTTPS URL 3. Copy the **Signing Secret** for signature verification 4. Select event types: new messages, reactions, button presses, form submissions, etc. 5. Add the bot to chats where you want to receive events (global events like company member changes work without adding to chat) ### TypeScript webhook handler (Express.js) ```typescript import express from "express" import crypto from "crypto" const SIGNING_SECRET = "your_signing_secret" // From bot settings → Outgoing Webhook const app = express() app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { // Step 1: Verify HMAC-SHA256 signature const signature = crypto.createHmac("sha256", SIGNING_SECRET) .update(req.body).digest("hex") if (signature !== req.headers["pachca-signature"]) { return res.status(401).send("Invalid signature") } // Step 2: Check timestamp for replay protection (±60 seconds) const event = JSON.parse(req.body.toString()) if (Math.abs(Date.now() / 1000 - event.webhook_timestamp) > 60) { return res.status(401).send("Expired event") } // Step 3: Process event by type switch (event.type) { case "message": if (event.event === "new") console.log("New message:", event.content, "from user:", event.user_id) if (event.event === "update") console.log("Message edited:", event.id) if (event.event === "delete") console.log("Message deleted:", event.id) break case "reaction": console.log(event.event === "new" ? "Reaction added:" : "Reaction removed:", event.emoji) break case "button": console.log("Button pressed:", event.data, "by user:", event.user_id) // Use event.trigger_id within 3 seconds to open a form break case "view_submit": console.log("Form submitted:", event.payload) break } res.status(200).send("OK") }) app.listen(3000) ``` ### Python webhook handler (Flask) ```python import hmac, hashlib, json, time from flask import Flask, request, abort SIGNING_SECRET = "your_signing_secret" # From bot settings → Outgoing Webhook app = Flask(__name__) @app.route("/webhook", methods=["POST"]) def webhook(): raw_body = request.get_data() # Step 1: Verify HMAC-SHA256 signature expected = hmac.new(SIGNING_SECRET.encode(), raw_body, hashlib.sha256).hexdigest() if expected != request.headers.get("Pachca-Signature"): abort(401) # Step 2: Check timestamp for replay protection event = json.loads(raw_body) if abs(time.time() - event["webhook_timestamp"]) > 60: abort(401) # Step 3: Process event by type if event["type"] == "message" and event["event"] == "new": print("New message:", event["content"], "from user:", event["user_id"]) elif event["type"] == "button": print("Button pressed:", event["data"]) return "OK", 200 ``` ### Webhook event types | Event type | Description | Fields | |-----------|-------------|--------| | message (new) | New message in chat | id, content, user_id, chat_id, entity_type, entity_id, created_at, url | | message (update) | Message edited | id, content, user_id, chat_id | | message (delete) | Message deleted | id, user_id, chat_id | | reaction (new/delete) | Reaction added/removed | message_id, user_id, emoji, chat_id | | button | Button pressed | message_id, user_id, data, trigger_id, chat_id | | view_submit | Form submitted | payload (form field values), user_id, trigger_id | | chat_member (new/delete) | Member added/removed from chat | chat_id, user_id, event | | company_member (new/update/delete) | Workspace member changes | user_id, event (no chat needed) | | link_shared | URL shared (unfurl bots) | url, message_id, chat_id | ### Alternative: Polling (when webhook URL is not available) Enable "Save event history" in bot settings, then poll: ```typescript // Poll for events periodically const events = await client.bots.getWebhookEvents() for (const event of events.data) { processEvent(event) await client.bots.deleteWebhookEvent(event.id) // Remove processed event } ``` ## How to handle rate limits and implement retry logic Both SDKs handle retry automatically — 3 retries with exponential backoff for `429` and `5xx` errors. No extra code needed: ### TypeScript SDK ```typescript import { PachcaClient, ApiError } from "@pachca/sdk" const client = new PachcaClient("YOUR_TOKEN") // SDK retries 429 and 5xx automatically (3 attempts, exponential backoff) // Just call methods normally — retries are transparent const users = await client.users.listUsersAll() // If all retries are exhausted, ApiError is thrown try { await client.messages.createMessage({ message: { entityId: 12345, content: "Hello" } }) } catch (error) { if (error instanceof ApiError) { // After 3 retries, the error is surfaced — check error.errors for details for (const e of error.errors ?? []) { console.error(e.key, e.message) } } } ``` ### Python SDK ```python from pachca.client import PachcaClient from pachca.models import ApiError client = PachcaClient("YOUR_TOKEN") # SDK retries 429 and 5xx automatically (3 attempts, exponential backoff) # Just call methods normally — retries are transparent users = await client.users.list_users_all() # If all retries are exhausted, ApiError is raised try: await client.messages.create_message(request) except ApiError as e: # After 3 retries, the error is surfaced — check e.errors for details for err in e.errors: print(err.key, err.message) ``` Rate limits by endpoint category: - Messages send/edit/delete: ~4 req/sec per chat (burst: 30/sec for 5s) - Messages read: ~10 req/sec per token - All other endpoints: ~50 req/sec per token - On 429: SDK respects `Retry-After` header automatically ## How to upload files and attach to messages File upload is a 3-step process: get presigned params → upload to S3 → attach to message. ### TypeScript SDK ```typescript import { PachcaClient, FileType } from "@pachca/sdk" import fs from "fs" import path from "path" const client = new PachcaClient("YOUR_TOKEN") const filePath = "./report.pdf" const fileName = path.basename(filePath) const fileBuffer = fs.readFileSync(filePath) // Step 1: Get S3 presigned upload parameters const params = await client.common.getUploadParams() // Step 2: Upload file to S3 (direct_url is an external presigned URL, not Pachca API) await client.common.uploadFile(params.directUrl, { contentDisposition: params.contentDisposition, acl: params.acl, policy: params.policy, xAmzCredential: params.xAmzCredential, xAmzAlgorithm: params.xAmzAlgorithm, xAmzDate: params.xAmzDate, xAmzSignature: params.xAmzSignature, key: params.key, file: new File([fileBuffer], fileName) }) // Step 3: Attach file to message (replace ${filename} in key with actual name) const fileKey = params.key.replace("${filename}", fileName) await client.messages.createMessage({ message: { entityId: 12345, content: "Report attached", files: [{ key: fileKey, name: fileName, fileType: FileType.File, size: fileBuffer.length }] } }) ``` ### Python SDK ```python from pachca.client import PachcaClient from pachca.models import ( FileUploadRequest, MessageCreateRequest, MessageCreateRequestMessage, MessageCreateRequestFile, FileType ) import os client = PachcaClient("YOUR_TOKEN") file_path = "report.pdf" file_name = os.path.basename(file_path) # Step 1: Get S3 presigned upload parameters params = await client.common.get_upload_params() # Step 2: Upload file to S3 (direct_url is an external presigned URL, not Pachca API) with open(file_path, "rb") as f: await client.common.upload_file( direct_url=params.direct_url, request=FileUploadRequest( content_disposition=params.content_disposition, acl=params.acl, policy=params.policy, x_amz_credential=params.x_amz_credential, x_amz_algorithm=params.x_amz_algorithm, x_amz_date=params.x_amz_date, x_amz_signature=params.x_amz_signature, key=params.key, file=f.read() ) ) # Step 3: Attach file to message (replace ${filename} in key with actual name) file_key = params.key.replace("${filename}", file_name) await client.messages.create_message(MessageCreateRequest( message=MessageCreateRequestMessage( entity_id=12345, content="Report attached", files=[MessageCreateRequestFile(key=file_key, name=file_name, file_type=FileType.FILE, size=os.path.getsize(file_path))] ) )) ``` File types: `FileType.File` / `FileType.FILE` (any file), `FileType.Image` / `FileType.IMAGE` (add `width` and `height`). ## How to monitor user status and presence ### TypeScript SDK ```typescript import { PachcaClient } from "@pachca/sdk" const client = new PachcaClient("YOUR_TOKEN") // Get any user's status const status = await client.users.getUserStatus(userId) console.log(status.emoji, status.title, status.isAway, status.awayMessage) // Set your own status await client.profile.updateStatus({ status: { emoji: "🏖️", title: "On vacation", isAway: true, awayMessage: "Back on Monday" } }) // Set status with expiration await client.profile.updateStatus({ status: { emoji: "🍽️", title: "Lunch break", expiresAt: new Date(Date.now() + 3600000).toISOString() } }) // Clear your status await client.profile.deleteStatus() ``` ### Python SDK ```python from pachca.client import PachcaClient from pachca.models import StatusUpdateRequest, StatusUpdateRequestStatus client = PachcaClient("YOUR_TOKEN") # Get any user's status status = await client.users.get_user_status(user_id) print(status.emoji, status.title, status.is_away, status.away_message) # Set your own status await client.profile.update_status(StatusUpdateRequest( status=StatusUpdateRequestStatus(emoji="🏖️", title="On vacation", is_away=True, away_message="Back on Monday") )) # Clear your status await client.profile.delete_status() ``` ### Polling pattern for monitoring status changes Pachca does not provide real-time webhooks for status/presence changes. Use polling with caching: ```typescript const cache = new Map() const CACHE_TTL = 60_000 // Minimum 60 seconds between polls per user async function getUserPresence(userId: number) { const cached = cache.get(userId) if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) return cached.status const status = await client.users.getUserStatus(userId) cache.set(userId, { status, fetchedAt: Date.now() }) return status } // Batch refresh for a team async function refreshTeamPresences(userIds: number[]) { const results = [] for (const id of userIds) { cache.delete(id) results.push(await getUserPresence(id)) } return results } ``` Status fields: `emoji` (string), `title` (string), `expires_at` (ISO datetime or null), `is_away` (boolean), `away_message` (string). Admin can manage any user's status via PUT/DELETE /users/{id}/status. ## How to handle idempotency and partial failures in multi-service workflows Pachca API is **NOT idempotent** — duplicate POST requests create duplicate resources. Webhooks use **at-least-once** delivery. Both require explicit handling in distributed systems. ### 1. Client-side request deduplication ```typescript import { PachcaClient, ApiError } from "@pachca/sdk" const client = new PachcaClient("YOUR_TOKEN") // Idempotency layer: track requests to prevent duplicates const requestLog = new Map() const DEDUP_TTL = 300_000 // 5 minutes async function idempotentCreateMessage(idempotencyKey: string, entityId: number, content: string) { // Check if this request was already processed const existing = requestLog.get(idempotencyKey) if (existing && Date.now() < existing.expiresAt) { return existing.result // Return cached result, no duplicate created } const result = await client.messages.createMessage({ message: { entityId, content } }) // Cache result with TTL for deduplication window requestLog.set(idempotencyKey, { result, expiresAt: Date.now() + DEDUP_TTL }) return result } // Usage: same key = same result, no duplicate message await idempotentCreateMessage("onboard-user-42", chatId, "Welcome!") await idempotentCreateMessage("onboard-user-42", chatId, "Welcome!") // returns cached, no duplicate ``` ### 2. Webhook event deduplication ```typescript // Webhooks deliver at-least-once — same event may arrive multiple times const processedEvents = new Set() function handleWebhookEvent(event: any) { // Build unique key from event fields: type + event + id const eventKey = `${event.type}:${event.event}:${event.id}:${event.webhook_timestamp}` if (processedEvents.has(eventKey)) { return // Already processed, skip duplicate } processedEvents.add(eventKey) // Process event (idempotent handler) switch (event.type) { case "message": syncMessageToExternalSystem(event) break case "button": handleButtonPress(event) break } } ``` ### 3. Saga pattern for multi-step operations ```typescript import { PachcaClient, MessageEntityType } from "@pachca/sdk" const client = new PachcaClient("YOUR_TOKEN") // Saga: create project workspace (chat + members + welcome message + task) // Each step has a compensating action for rollback on failure interface SagaStep { name: string execute: () => Promise compensate: (result: any) => Promise } async function executeSaga(steps: SagaStep[]) { const completed: { step: SagaStep; result: any }[] = [] for (const step of steps) { try { const result = await step.execute() completed.push({ step, result }) } catch (error) { console.error(`Saga failed at step "${step.name}":`, error) // Compensate in reverse order (undo completed steps) for (const { step: completedStep, result } of completed.reverse()) { try { await completedStep.compensate(result) console.log(`Compensated: ${completedStep.name}`) } catch (compensateError) { // Log for manual intervention — compensation itself failed console.error(`CRITICAL: Failed to compensate "${completedStep.name}":`, compensateError) } } throw new Error(`Saga rolled back at step "${step.name}"`) } } return completed.map(c => c.result) } // Define saga steps with compensating actions async function createProjectWorkspace(projectName: string, memberIds: number[]) { let chatId: number const steps: SagaStep[] = [ { name: "create-chat", execute: async () => { const chat = await client.chats.createChat({ chat: { name: projectName, memberIds, channel: false, public: false } }) chatId = chat.id return chat }, compensate: async (chat) => { // No delete chat API — archive instead await client.chats.archiveChat(chat.id) }, }, { name: "send-welcome", execute: async () => { return await client.messages.createMessage({ message: { entityType: MessageEntityType.Discussion, entityId: chatId, content: `Project **${projectName}** workspace created!` } }) }, compensate: async (message) => { await client.messages.deleteMessage(message.id) }, }, { name: "create-task", execute: async () => { return await client.tasks.createTask({ task: { kind: "reminder", content: `Set up ${projectName}`, performerIds: memberIds } }) }, compensate: async (task) => { await client.tasks.deleteTask(task.id) }, }, ] return executeSaga(steps) } ``` ### 4. State reconciliation across microservices ```typescript // Periodic reconciliation: ensure external system matches Pachca state async function reconcileUsers(externalDb: Map) { const pachcaUsers = await client.users.listUsersAll() for (const user of pachcaUsers) { const external = externalDb.get(user.id) if (!external) { // User exists in Pachca but not in external system — sync await syncUserToExternal(user) } else if (external.lastActivityAt < user.lastActivityAt) { // Pachca has newer activity — update external await updateExternalUser(user) } } // Check for users in external system that no longer exist in Pachca for (const [id, external] of externalDb) { if (!pachcaUsers.find(u => u.id === id)) { await markExternalUserDeleted(id) } } } // Schedule reconciliation as fallback for missed webhooks setInterval(() => reconcileUsers(externalDb), 15 * 60 * 1000) // Every 15 minutes ``` ### Key principles for Pachca API distributed systems - **All POST endpoints create new resources** — never assume idempotency, always deduplicate on client side - **Webhooks are at-least-once** — handlers MUST be idempotent (dedup by event id + type + timestamp) - **Separate critical from non-critical operations** — chat creation (critical) vs welcome message (non-critical) should fail independently - **Use saga pattern with compensating actions** — for multi-step workflows where partial completion is worse than full rollback - **Implement periodic reconciliation** — webhooks can be missed; polling + reconciliation ensures eventual consistency - **Track operation state externally** — store saga progress in your database, not in memory, for crash recovery - **SDK auto-retry handles transient failures** — 429 and 5xx are retried 3 times automatically, so sagas should only compensate on permanent failures (4xx) --- --- # API Reference Base URL: `https://api.pachca.com/api/shared/v1` All endpoints require `Authorization: Bearer ` header. ## Common ### POST /api/shared/v1/chats/exports Request a message export for the specified time period. Body: { start_at*, end_at*, webhook_url*, chat_ids, skip_chats_file } ### GET /api/shared/v1/chats/exports/{id} Download a completed message export archive. To download the archive you need to know its `id` and specify it in the request `URL`. The server will respond with `302 Found` and a `Location` header containing a temporary download link. Most HTTP clients automatically follow the redirect and download the file. Parameters: id ### GET /api/shared/v1/custom_properties Working with "File" type custom properties is currently unavailable. Retrieve the current list of custom properties for employees and tasks in your workspace. By default, all entities in your workspace only have basic fields. However, your workspace administrator can add, edit, and delete custom properties. If you use custom properties that are no longer current (deleted or non-existent) when creating employees (or tasks), you will receive an error. Parameters: entity_type ### POST /api/shared/v1/direct_url Upload a file to the server using `multipart/form-data` format. Upload parameters are obtained via the [Get signature, key and other parameters](POST /uploads) method. Body: { Content-Disposition*, acl*, policy*, x-amz-credential*, x-amz-algorithm*, x-amz-date*, x-amz-signature*, key*, file* } ### POST /api/shared/v1/uploads Retrieve the signature, key, and other parameters required for file upload. This method must be used for each file upload. ## Profile ### GET /api/shared/v1/oauth/token/info Retrieve information about the current OAuth token, including its scopes, creation date, and last usage date. The token in the response is masked — only the first 8 and last 4 characters are visible. ### GET /api/shared/v1/profile Retrieve information about your own profile. ### PUT /api/shared/v1/profile/avatar Upload or update your profile avatar. The file is sent in `multipart/form-data` format. Body: { image* } ### DELETE /api/shared/v1/profile/avatar Delete your profile avatar. ### GET /api/shared/v1/profile/status Retrieve information about your current status. ### PUT /api/shared/v1/profile/status Set a new status for yourself. Body: { status: { emoji*, title*, expires_at, is_away, away_message } } ### DELETE /api/shared/v1/profile/status Delete your current status. ## Users ### GET /api/shared/v1/users Retrieve the current list of employees in your workspace. Parameters: query?, limit?, cursor? ### POST /api/shared/v1/users Create a new employee in your workspace. You can fill in custom properties for the employee that have been created in your workspace. You can get the current list of employee custom property IDs using the [List custom properties](GET /custom_properties) method. Body: { user*, skip_email_notify } ### GET /api/shared/v1/users/{id} Retrieve information about an employee. To get an employee you need to know their `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/users/{id} Edit an employee. To edit an employee you need to know their `id` and specify it in the request `URL`. All editable employee parameters are specified in the request body. You can get the current list of employee custom property IDs using the [List custom properties](GET /custom_properties) method. Parameters: id Body: { user: { first_name, last_name, email, phone_number, nickname, department, title, role, suspended, list_tags, custom_properties } } ### DELETE /api/shared/v1/users/{id} Delete an employee. To delete an employee you need to know their `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/users/{user_id}/avatar Upload or update an employee's avatar. The file is sent in `multipart/form-data` format. Parameters: user_id Body: { image* } ### DELETE /api/shared/v1/users/{user_id}/avatar Delete an employee's avatar. Parameters: user_id ### GET /api/shared/v1/users/{user_id}/status Retrieve information about an employee's status. Parameters: user_id ### PUT /api/shared/v1/users/{user_id}/status Set a new status for an employee. Parameters: user_id Body: { status: { emoji*, title*, expires_at, is_away, away_message } } ### DELETE /api/shared/v1/users/{user_id}/status Delete an employee's status. Parameters: user_id ## Group tags ### GET /api/shared/v1/group_tags Retrieve the current list of employee tags. Tag names are unique within the workspace. Parameters: names?, limit?, cursor? ### POST /api/shared/v1/group_tags Create a new tag. Body: { group_tag: { name* } } ### GET /api/shared/v1/group_tags/{id} Retrieve information about a tag. Tag names are unique within the workspace. To get a tag you need to know its `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/group_tags/{id} Edit a tag. To edit a tag you need to know its `id` and specify it in the request `URL`. All editable tag parameters are specified in the request body. Parameters: id Body: { group_tag: { name* } } ### DELETE /api/shared/v1/group_tags/{id} Delete a tag. To delete a tag you need to know its `id` and specify it in the request `URL`. Parameters: id ### GET /api/shared/v1/group_tags/{id}/users Retrieve the current list of employees in a tag. Parameters: id, limit?, cursor? ## Chats ### GET /api/shared/v1/chats Retrieve a list of chats based on the specified parameters. Parameters: sort?, order?, availability?, last_message_at_after?, last_message_at_before?, personal?, limit?, cursor? ### POST /api/shared/v1/chats Create a new chat. To create a one-on-one direct message with a user, use the [New message](POST /messages) method. When creating a chat, you automatically become a member. Body: { chat: { name*, member_ids, group_tag_ids, channel, public } } ### GET /api/shared/v1/chats/{id} Retrieve information about a chat. To get a chat you need to know its `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/chats/{id} Update chat parameters. To update a chat you need to know its `id` and specify it in the `URL`. All updatable fields are passed in the request body. Parameters: id Body: { chat: { name, public } } ### PUT /api/shared/v1/chats/{id}/archive Archive a chat. To archive a chat you need to know its `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/chats/{id}/unarchive Restore a chat from the archive. To unarchive a chat you need to know its `id` and specify it in the request `URL`. Parameters: id ## Members ### POST /api/shared/v1/chats/{id}/group_tags Add tags to the member list of a conversation or channel. After adding a tag, all its members automatically become members of the chat. The tag and chat member lists are synchronized automatically: when a new member is added to the tag, they immediately appear in the chat; when removed from the tag, they are removed from the chat. Parameters: id Body: { group_tag_ids* } ### DELETE /api/shared/v1/chats/{id}/group_tags/{tag_id} Remove a tag from the member list of a conversation or channel. To remove a tag you need to know its `id` and specify it in the request `URL`. Parameters: id, tag_id ### DELETE /api/shared/v1/chats/{id}/leave Leave a conversation or channel on your own. Parameters: id ### GET /api/shared/v1/chats/{id}/members Retrieve the current list of chat members. The workspace owner can retrieve the member list of any chat in the workspace. Admins and bots can only retrieve the member list of chats they belong to (or that are public). Parameters: id, role?, limit?, cursor? ### POST /api/shared/v1/chats/{id}/members Add users to the member list of a conversation, channel, or thread. Parameters: id Body: { member_ids*, silent } ### PUT /api/shared/v1/chats/{id}/members/{user_id} Edit a user's or bot's role in a conversation or channel. To edit a role in a conversation or channel you need to know the `id` of the chat and the user (or bot) and specify them in the request `URL`. All editable role parameters are specified in the request body. The chat owner's role cannot be changed. They always have Admin privileges in the chat. Parameters: id, user_id Body: { role* } ### DELETE /api/shared/v1/chats/{id}/members/{user_id} Remove a user from the member list of a conversation or channel. If the user is the chat owner, they cannot be removed. They can only leave the chat on their own using the [Leave conversation or channel](DELETE /chats/{id}/leave) method. Parameters: id, user_id ## Threads ### POST /api/shared/v1/messages/{id}/thread Create a new thread on a message. If a thread has already been created for the message, the response will return information about the previously created thread. Parameters: id ### GET /api/shared/v1/threads/{id} Retrieve information about a thread. To get a thread you need to know its `id` and specify it in the request `URL`. Parameters: id ## Messages ### GET /api/shared/v1/messages Retrieve a list of messages from conversations, channels, threads, and direct messages. To retrieve messages you need to know the `chat_id` of the required conversation, channel, thread, or direct message, and specify it in the request `URL`. Messages are returned in descending order by send date (i.e., the most recent messages come first). To retrieve earlier messages, use the `limit` and `cursor` parameters. Parameters: chat_id, sort?, order?, limit?, cursor? ### POST /api/shared/v1/messages Send a message to a conversation or channel, a direct message to a user, or a comment to a thread. When using `entity_type: "discussion"` (or simply without specifying `entity_type`), any `chat_id` can be passed in the `entity_id` field. This means you can send a message knowing only the chat ID. Additionally, you can send a message to a thread by its ID or a direct message by the user's ID. To send a direct message to a user, you do not need to create a chat. Simply specify `entity_type: "user"` and the user's ID. A chat will be created automatically if there has been no prior conversation between you. Only one direct chat can exist between two users. Body: { message*, link_preview } ### GET /api/shared/v1/messages/{id} Retrieve information about a message. To get a message you need to know its `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/messages/{id} Edit a message or comment. To edit a message you need to know its `id` and specify it in the request `URL`. All editable message parameters are specified in the request body. Parameters: id Body: { message: { content, files, buttons, display_avatar_url, display_name } } ### DELETE /api/shared/v1/messages/{id} Delete a message. Message deletion is available to the sender, admins, and editors in the chat. In direct messages, both users are editors. There are no time restrictions on message deletion. To delete a message you need to know its `id` and specify it in the request `URL`. Parameters: id ### POST /api/shared/v1/messages/{id}/pin Pin a message in a chat. To pin a message you need to know the message `id` and specify it in the request `URL`. Parameters: id ### DELETE /api/shared/v1/messages/{id}/pin Unpin a message from a chat. To unpin a message you need to know the message `id` and specify it in the request `URL`. Parameters: id ## Read members ### GET /api/shared/v1/messages/{id}/read_member_ids Retrieve the current list of users who have read the message. Parameters: id, limit?, cursor? ## Reactions ### GET /api/shared/v1/messages/{id}/reactions Retrieve the current list of reactions on a message. Parameters: id, limit?, cursor? ### POST /api/shared/v1/messages/{id}/reactions Add a reaction to a message. To add a reaction you need to know the message `id` and specify it in the request `URL`. Message reactions are sent as `Emoji` characters. If the user has already added the same reaction, it will not be duplicated. To remove a reaction, use the [Delete reaction](DELETE /messages/{id}/reactions) method. **Reaction limits:** - Each user can add no more than **20 unique** reactions - A message can have no more than **30 unique** reactions - The total number of reactions on a message cannot exceed **1000** Parameters: id Body: { code*, name } ### DELETE /api/shared/v1/messages/{id}/reactions Remove a reaction from a message. To remove a reaction you need to know the message `id` and specify it in the request `URL`. Message reactions are stored as `Emoji` characters. You can only remove reactions that were added by the authenticated user. Parameters: id, code, name? ## Link Previews ### POST /api/shared/v1/messages/{id}/link_previews Create link previews in messages. Only available for Unfurl bots. Parameters: id Body: { link_previews* } ## Search ### GET /api/shared/v1/search/chats Full-text search for channels and conversations. Parameters: query?, limit?, cursor?, order?, created_from?, created_to?, active?, chat_subtype?, personal? ### GET /api/shared/v1/search/messages Full-text search for messages. Parameters: query?, limit?, cursor?, order?, created_from?, created_to?, chat_ids?, user_ids?, active? ### GET /api/shared/v1/search/users Full-text search for employees by name, email, position, and other fields. Parameters: query?, limit?, cursor?, sort?, order?, created_from?, created_to?, company_roles? ## Tasks ### GET /api/shared/v1/tasks Retrieve a list of tasks. Parameters: limit?, cursor? ### POST /api/shared/v1/tasks Create a new task. When creating a task, specifying the task type is required: call, meeting, simple reminder, event, or email. No additional description is needed — you simply create a task with the corresponding text. If you specify a task description, it will become the task's text. A task must have assignees; if none are specified, you are assigned as the responsible person. Any workspace employee can be assigned to a task that is not linked to any entity. You can get the current employee list using the [List employees](GET /users) method. A task can be linked to a chat by specifying `chat_id`. To link to a chat, you must be a member of that chat. Body: { task: { kind*, content, due_at, priority, performer_ids, chat_id, all_day, custom_properties } } ### GET /api/shared/v1/tasks/{id} Retrieve information about a task. To get a task you need to know its `id` and specify it in the request `URL`. Parameters: id ### PUT /api/shared/v1/tasks/{id} Edit a task. To edit a task you need to know its `id` and specify it in the request `URL`. All editable task parameters are specified in the request body. Parameters: id Body: { task: { kind, content, due_at, priority, performer_ids, status, all_day, done_at, custom_properties } } ### DELETE /api/shared/v1/tasks/{id} Delete a task. To delete a task you need to know its `id` and specify it in the request `URL`. Parameters: id ## Views ### POST /api/shared/v1/views/open Open a modal window with a view for the user. To open a modal window with a view, your application must have a valid, non-expired `trigger_id`. Body: { type*, trigger_id*, private_metadata, callback_id, view* } ## Bots ### PUT /api/shared/v1/bots/{id} Edit a bot's settings. To edit a bot you need to know its `user_id` and specify it in the request `URL`. All editable bot parameters are specified in the request body. You can find the bot's `user_id` in the bot settings under the "API" tab. You cannot edit a bot whose settings are not accessible to you (the "Who can edit bot settings" field is located on the "General" tab in the bot settings). Parameters: id Body: { bot: { webhook* } } ### GET /api/shared/v1/webhooks/events Retrieve the history of a bot's recent events. This method is useful if you cannot receive events in real time at your `URL`, but need to process all events you have subscribed to. Event history is only saved when the "Save event history" option is enabled in the "Outgoing webhook" tab of the bot settings. Specifying a "Webhook `URL`" is not required. To retrieve the event history of a specific bot, you need to know its `access_token` and use it in the request. Each event is a webhook `JSON` object. Parameters: limit?, cursor? ### DELETE /api/shared/v1/webhooks/events/{id} This method is only available with a bot's `access_token`. Delete an event from the bot's event history. To delete an event you need to know the bot's `access_token` that owns the event and the event `id`. Parameters: id ## Security ### GET /api/shared/v1/audit_events Retrieve event logs based on the specified filters. Parameters: start_time?, end_time?, event_key?, actor_id?, actor_type?, entity_id?, entity_type?, limit?, cursor?