# Create organization Source: https://docs.zenzap.co/api-reference/agentic/create-organization /openapi.yaml post /v2/agentic/organization/create Allows an external AI agent to programmatically create a Zenzap organization, install a bot (the agent itself), and invite a human user — all in a single request. **Authentication:** None required. This endpoint is publicly accessible. **Rate limit:** 1 request per minute per IP. Supports two content types: - `application/json` — for requests without a logo - `multipart/form-data` — for requests that include a company logo (`filePart`) Once you receive the `201` response, use the returned `credentials` to authenticate all subsequent API calls. See [Authentication](/api-reference/authentication) for details. # Authentication Source: https://docs.zenzap.co/api-reference/authentication Call the Zenzap API with your bot's credentials This page assumes you've already created a bot in the Zenzap console. If not, start at [Setup](/api-reference/setup-steps) — that's where you pick a credential type. Open the tab that matches your bot's credential type. Every request carries three headers: ``` Authorization: Bearer YOUR_API_KEY X-Signature: X-Timestamp: ``` The signature payload differs by HTTP method: * **POST/PUT/PATCH/DELETE**: sign `{timestamp}.{body}` * **GET**: sign `{timestamp}.{uri}` (full path + query string) For `multipart/form-data` requests, sign the exact raw request body bytes with a timestamp prefix: `{timestamp}.`. Requests with timestamps older than 5 minutes are rejected. ### How to calculate 1. Get the current Unix timestamp in milliseconds. 2. Build the payload: * **POST/PUT/PATCH/DELETE**: `{timestamp}.{raw-body}` * **GET**: `{timestamp}.{uri}` (for example `/v2/members?limit=10&offset=0`) 3. Calculate HMAC-SHA256 of the payload using your API secret. 4. Hex-encode the digest (64 lowercase chars). 5. Send both `X-Signature` and `X-Timestamp`. ### Example — POST (Python) ```python theme={"theme":"github-dark"} import json import hmac import hashlib import time import requests def create_topic(name: str, members: list[str]) -> dict: body = {"name": name, "members": members} body_json = json.dumps(body, separators=(",", ":")) timestamp = int(time.time() * 1000) signature_payload = f"{timestamp}.{body_json}" signature = hmac.new( API_SECRET.encode(), signature_payload.encode(), hashlib.sha256, ).hexdigest() headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "X-Signature": signature, "X-Timestamp": str(timestamp), } response = requests.post(f"{BASE_URL}/v2/topics", headers=headers, data=body_json) response.raise_for_status() return response.json() ``` ### Example — GET (Python) ```python theme={"theme":"github-dark"} import hmac import hashlib import time import requests def get_topic(topic_id: str) -> dict: uri_path = f"/v2/topics/{topic_id}" timestamp = int(time.time() * 1000) signature_payload = f"{timestamp}.{uri_path}" signature = hmac.new( API_SECRET.encode(), signature_payload.encode(), hashlib.sha256, ).hexdigest() headers = { "Authorization": f"Bearer {API_KEY}", "X-Signature": signature, "X-Timestamp": str(timestamp), } response = requests.get(f"{BASE_URL}/v2/topics/{topic_id}", headers=headers) response.raise_for_status() return response.json() ``` Our API documentation tools cannot automatically generate HMAC signatures. Calculate the signature manually or use a tool like Postman with pre-request scripts. Exchange your `clientId` + `clientSecret` for a short-lived bearer access token, then call the API with that token. Only the `client_credentials` grant is supported. ### Token endpoint Send the token request parameters as URL-encoded form fields, not as JSON or a raw request body. ```http theme={"theme":"github-dark"} POST /oauth/token HTTP/1.1 Host: api.zenzap.co Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &client_id= &client_secret= &scope=channel:list+message:send ``` `scope` is optional. If omitted, the token receives every scope configured on the bot. Pass a space-separated subset to down-scope. HTTP Basic Auth is also accepted — send `Authorization: Basic base64(clientId:clientSecret)` and omit `client_id` + `client_secret` from the form body. #### Success response ```json theme={"theme":"github-dark"} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "scope": "channel:list message:send" } ``` * `expires_in` is the token lifetime in seconds (default 3600 = 1 hour). * There is **no refresh token**. When the access token expires, call `/oauth/token` again. #### Error response Errors follow RFC 6749 §5.2: ```json theme={"theme":"github-dark"} { "error": "invalid_grant", "error_description": "invalid client credentials or scopes" } ``` | Error code | Meaning | | ------------------------ | ------------------------------------------------------------- | | `invalid_request` | Malformed request body or missing required field | | `invalid_client` | `client_id` / `client_secret` missing or wrong | | `invalid_grant` | Credentials or requested scopes are not valid for this client | | `unsupported_grant_type` | Only `client_credentials` is supported | ### Calling the API Pass the JWT as a Bearer token on every request: ```http theme={"theme":"github-dark"} GET /v2/topics HTTP/1.1 Host: api.zenzap.co Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` Do **not** include `X-Signature` or `X-Timestamp` on OAuth requests — those headers are only used by the static-API-key flow. If the token is missing, expired, revoked, or its bot has been deactivated, the API returns `401 Unauthorized` with an RFC 6750 `WWW-Authenticate` challenge: ``` WWW-Authenticate: Bearer realm="zenzap", error="invalid_token", error_description="Invalid Bearer token" ``` If the token is valid but lacks the scope required by the endpoint, the API returns `403 Forbidden`: ``` WWW-Authenticate: Bearer realm="zenzap", error="insufficient_scope", scope="message:send" ``` ### End-to-end example (Python) ```python theme={"theme":"github-dark"} import os import requests BASE_URL = "https://api.zenzap.co" CLIENT_ID = os.environ["ZENZAP_CLIENT_ID"] CLIENT_SECRET = os.environ["ZENZAP_CLIENT_SECRET"] def get_access_token() -> str: """Exchange client credentials for a short-lived bearer token.""" resp = requests.post( f"{BASE_URL}/oauth/token", data={ "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "scope": "channel:list message:send", }, ) resp.raise_for_status() return resp.json()["access_token"] def send_message(token: str, topic_id: str, text: str) -> dict: resp = requests.post( f"{BASE_URL}/v2/messages", headers={"Authorization": f"Bearer {token}"}, json={"topicId": topic_id, "text": text}, ) resp.raise_for_status() return resp.json() if __name__ == "__main__": token = get_access_token() send_message(token, "550e8400-e29b-41d4-a716-446655440000", "Hello from OAuth!") ``` In production, cache the access token for slightly less than `expires_in` and re-mint on demand rather than on every request. ### Rotating the `clientSecret` Rotate from the bot's detail screen in the Zenzap console. After rotation: * The new `clientSecret` is returned **once** in the rotate response. Save it immediately. * Tokens minted with the old secret continue to work until they expire (up to 1 h). * Future `/oauth/token` calls with the old secret return `invalid_grant`. ## OAuth scopes OAuth bots are authorized by scope, not by `read` / `write`. Each `/v2/*` endpoint requires a specific scope — the bot must be granted that scope at creation, and the access token must include it. | Scope | Grants access to | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `channel:list` | `GET /v2/topics` | | `channel:read` | `GET /v2/topics/{topicId}`, `GET /v2/topics/external/{externalId}` | | `channel:write` | `POST /v2/topics`, `PATCH /v2/topics/{topicId}`, `POST/DELETE /v2/topics/{topicId}/members`, `POST/DELETE /v2/topics/{topicId}/labels`, `PATCH /v2/topics/{topicId}/cover-image` | | `message:read` | `GET /v2/messages/{messageId}`, `GET /v2/topics/{topicId}/messages` | | `message:send` | `POST /v2/messages` | | `message:write` | `PATCH /v2/messages/{messageId}`, `DELETE /v2/messages/{messageId}`, `POST /v2/messages/{messageId}/delivered`, `POST /v2/messages/{messageId}/read` | | `reaction:write` | `POST /v2/messages/{messageId}/reactions`, `DELETE /v2/messages/{messageId}/reactions/{reactionId}` | | `task:read` | `GET /v2/tasks`, `GET /v2/tasks/{taskId}` | | `task:write` | `POST /v2/tasks`, `PATCH /v2/tasks/{taskId}`, `DELETE /v2/tasks/{taskId}` | | `poll:write` | `POST /v2/polls`, `POST/DELETE /v2/polls/{pollId}/votes/...` | | `member:read` | `GET /v2/members`, `GET /v2/members/me` | | `updates:read` | `GET /v2/updates` | | `org_unit:read` | `GET /v2/org-units`, `GET /v2/org-units/{id}`, `GET /v2/org-units/{id}/members` | | `org_unit:write` | `POST/DELETE /v2/org-units/{id}/members` | | `label:read` | `GET /v2/organization/labels` | | `label:write` | `POST /v2/organization/labels` | A token may carry multiple scopes. Request the minimum set you need — narrower scopes limit the blast radius if the token is ever leaked. # Getting Started Source: https://docs.zenzap.co/api-reference/getting-started Get started with the Zenzap External Integration API Welcome to the Zenzap External Integration API documentation. This API is used to integrate with Zenzap. As a context we would like to familiarize you with the Zenzap platform and how it works. Zenzap is a platform for creating and managing topics and messages. * **Topics** (Zenzap term for group chats/channels/conversations) are used to create and manage conversations with your team. Topics are used to group messages and tasks together. * **Messages** are used to send and receive messages with your team. * **Tasks** are used to create and manage tasks with your team. * **Members** are used to manage your team members. * **API keys** are used to authenticate your requests to the Zenzap API. API keys are created and managed by the Zenzap admin user. When you create a new API key, it would create a new bot user in Zenzap, on which behalf you can send messages, create topics, create tasks and manage members. All actions you perform with the API key will be performed on behalf of the bot user. The bot can be used within the scope of your organization. You can create topics, send messages, create tasks and manage members on behalf of the bot. Each bot picks a credential type at creation: * **Static API key** — long-lived key signed with HMAC-SHA256 on every request. * **OAuth 2.0 client credentials** — short-lived bearer tokens minted from `clientId` + `clientSecret`. Either credential type works against the same `/v2/*` endpoints — see [Authentication](/api-reference/authentication) for both flows side by side. # Long Polling Source: https://docs.zenzap.co/api-reference/long-polling Fetch outbound events with GET /v2/updates Long polling lets your integration fetch outbound events instead of receiving webhooks. ## Requirements * API key delivery mode must be `polling` * A bot can use either polling or webhooks, not both at the same time * Requests use the same auth/signature flow as the rest of the API: * `Authorization: Bearer ` * `X-Timestamp: ` * `X-Signature: HMAC-SHA256("{timestamp}.{uri}")` for this GET endpoint ## Endpoint Use `GET /v2/updates`. Query parameters: | Parameter | Type | Description | | --------- | ------- | ---------------------------------------------------------------------------- | | `offset` | string | Opaque cursor from the previous response (`nextOffset`). Omit on first call. | | `limit` | integer | Max updates to return. Default `50`, max `100`. | | `timeout` | integer | Wait time in seconds when no updates are available. Default `0`, max `30`. | Behavior: * If updates exist after `offset`, response returns immediately * If no updates and `timeout > 0`, request waits up to `timeout` seconds * If still no updates, returns `updates: []` and a `nextOffset` ## Response Shape ```json theme={"theme":"github-dark"} { "updates": [ { "updateId": "AAAAAAABAAAAAAAAAAABAA==", "eventType": "message.created", "createdAt": 1699564800000, "data": { "message": { "id": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "senderId": "550e8400-e29b-41d4-a716-446655440001", "senderType": "user", "senderName": "Alice Johnson", "type": "text", "text": "Hello team!", "createdAt": 1699564800000 }, "truncated": false } } ], "nextOffset": "AAAAAAABAAAAAAAAAAABAA==" } ``` Use `nextOffset` from each response as the `offset` in your next request. ## Event Payloads Polling `data` payloads match webhook `data` payloads for the same event type. See [Webhook Events](/api-reference/webhook-events) for field-level payload details. ## Mentions Mentions are included in message events (`message.created`, `message.updated`). * `data.message.mentions`: mention objects (`id`, `name`) * `data.message.mentionedProfiles`: optional list of mentioned profile IDs Example: ```json theme={"theme":"github-dark"} { "eventType": "message.created", "data": { "message": { "text": "Hi <@550e8400-e29b-41d4-a716-446655440001>, can you review this?", "mentionedProfiles": ["550e8400-e29b-41d4-a716-446655440001"], "mentions": [ { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Alice Johnson" } ] } } } ``` `data.message.text` keeps the mention token format (`<@profileId>`), while `mentions[]` provides resolved display names. ## Attachments Attachments are included directly in polling message events (`message.created`, `message.updated`) under `data.message.attachments`. Attachment fields: | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------- | | `id` | string | Attachment ID | | `type` | string | `image`, `file`, `video`, `audio` | | `name` | string | Original filename (when available) | | `url` | string | Signed download URL | | `mimeType` | string | Stored MIME type when known (for example `video/mp4` for Giphy GIFs) | | `searchQuery` | string | Giphy picker search text or GIF title, omitted when empty | | `transcription` | object | Audio transcription status/details (voice messages only) | Example: ```json theme={"theme":"github-dark"} { "eventType": "message.created", "data": { "message": { "type": "audio", "attachments": [ { "id": "550e8400-e29b-41d4-a716-446655440088", "type": "audio", "name": "voice-note.mp3", "url": "https://storage.zenzap.co/attachments/...?token=...&expires=...", "transcription": { "status": "Pending" } } ] } } } ``` Notes: * Attachment `url` is signed and expires (currently 60 minutes) * Voice transcription is asynchronous: * `message.created` usually arrives with `transcription.status: "Pending"` * `message.updated` can arrive later with `status: "Done"` and `text` Supported event types: * `message.created` * `message.updated` * `message.deleted` * `reaction.added` * `reaction.removed` * `member.added` * `member.removed` * `topic.updated` ## Errors * `400` bad request (invalid `offset`, `limit`, or `timeout`) * `401` unauthorized * `409` delivery mode is not polling, or provided `offset` is no longer available * `500` internal server error # Get updates (long polling) Source: https://docs.zenzap.co/api-reference/long-polling/get-updates-long-polling /openapi.yaml get /v2/updates Retrieve outbound events for bots configured with **polling** delivery mode. Long polling behavior: - If updates are available after `offset`, they are returned immediately - If none are available and `timeout > 0`, the request waits up to `timeout` seconds - If still no updates, returns an empty list with `nextOffset` - Returns `409` if delivery mode is not polling, or if `offset` is no longer available Message attachment payloads are included inline in polling events, the same as webhooks: `data.message.attachments[]` with signed download URLs (including audio transcription metadata when available). Use `nextOffset` from each response as `offset` in the next request. # Get current member Source: https://docs.zenzap.co/api-reference/members/get-current-member /openapi.yaml get /v2/members/me Get information about the member associated with the current API key (the bot itself). # List members Source: https://docs.zenzap.co/api-reference/members/list-members /openapi.yaml get /v2/members Get a paginated list of all members in your organization. Only returns active members. Pagination: - `limit`: default `50`, max `100` - `cursor`: opaque cursor from the previous response (`nextCursor`) # Add a reaction to a message Source: https://docs.zenzap.co/api-reference/messages/add-a-reaction-to-a-message /openapi.yaml post /v2/messages/{messageId}/reactions Add an emoji reaction to a message. The bot must be a member of the channel. This endpoint is idempotent per `{messageId, bot, reaction}`: - Returns `201` when a new reaction is created - Returns `200` when the same reaction already exists for this bot # Delete a message Source: https://docs.zenzap.co/api-reference/messages/delete-a-message /openapi.yaml delete /v2/messages/{messageId} Delete a message by ID. Only the bot that sent the message can delete it. # Edit a message Source: https://docs.zenzap.co/api-reference/messages/edit-a-message /openapi.yaml patch /v2/messages/{messageId} Edit the text of a message. Only the bot that sent the message can edit it, and the bot must still be a member of the channel. Notes: - `text` is required and cannot be empty - Message length is limited to 10000 characters - Edited messages will have an `Edited` property marker applied # Mark a message as delivered Source: https://docs.zenzap.co/api-reference/messages/mark-a-message-as-delivered /openapi.yaml post /v2/messages/{messageId}/delivered Mark a message as delivered by the current bot. This endpoint is idempotent. If the message has not been fully persisted yet, delivery status is queued and applied when it becomes available. # Mark a message as read Source: https://docs.zenzap.co/api-reference/messages/mark-a-message-as-read /openapi.yaml post /v2/messages/{messageId}/read Mark a message as read by the current bot. This endpoint is idempotent. If the message has not been fully persisted yet, read status is queued and applied when it becomes available. # Remove a reaction from a message Source: https://docs.zenzap.co/api-reference/messages/remove-a-reaction-from-a-message /openapi.yaml delete /v2/messages/{messageId}/reactions/{reactionId} Remove a reaction by ID. Only the bot that added the reaction can remove it, and the bot must still be a member of the channel. The `messageId` in the path must match the message the reaction belongs to. # Send a message Source: https://docs.zenzap.co/api-reference/messages/send-a-message /openapi.yaml post /v2/messages Send a message to a topic. Your API key bot will appear as the sender. Supported request modes: - `application/json` for text messages - `multipart/form-data` for file/image messages Mentions: - Mention members directly in `text` using `<@profileId>` - Example: `Hello <@550e8400-e29b-41d4-a716-446655440001>` - Mentioned profiles must be members of the topic Limits: - message length is limited to 10000 characters # Issue an OAuth access token Source: https://docs.zenzap.co/api-reference/oauth/issue-an-oauth-access-token /openapi.yaml post /oauth/token Exchanges OAuth client credentials for a short-lived bearer access token. Only the **`client_credentials`** grant is supported on this endpoint. Use the `clientId` and `clientSecret` returned when the bot was created (or rotated) in your Zenzap admin console. **Client authentication**: either send `client_id` + `client_secret` in the form body, or use HTTP Basic Auth with the same values (`Authorization: Basic base64(clientId:clientSecret)`). **Scopes**: omit the `scope` field to receive a token with all scopes configured on the bot, or pass a space-separated subset to down-scope. Requesting a scope that the bot was not granted at creation will fail. **Token lifetime**: 1 hour. There is **no refresh token** — re-mint with the client credentials when the token expires. Tokens are JWTs bound to the issuing region and to the bot. They are validated on every request. See [Authentication](/api-reference/authentication) for the full reference. # Add members to an org unit Source: https://docs.zenzap.co/api-reference/org-units/add-members-to-an-org-unit /openapi.yaml post /v2/org-units/{orgUnitId}/members Add one or more members to an org unit. **Validation:** - All members must exist and be in your organization (returns 400 "Invalid member" otherwise) - Maximum 5 members per request; duplicates are removed **Behavior:** Idempotent — members already in the unit are ignored. **Rate limit:** 1 request per minute per bot, shared with the remove-members endpoint (returns 429 if exceeded). **Org-unit restriction:** If your organization restricts communication to within org units, your bot must be a member of the org unit; otherwise a 404 is returned. A bot that does not belong to any org unit cannot access any of them (every org unit returns 404). # Get an org unit Source: https://docs.zenzap.co/api-reference/org-units/get-an-org-unit /openapi.yaml get /v2/org-units/{orgUnitId} Get a single org unit in your organization, including its member IDs. **Note:** For security reasons, a 404 is returned both when the org unit does not exist and when it belongs to a different organization. **Org-unit restriction:** If your organization restricts communication to within org units, your bot must be a member of the org unit; otherwise a 404 is returned. A bot that does not belong to any org unit cannot access any of them (every org unit returns 404). # List org unit members Source: https://docs.zenzap.co/api-reference/org-units/list-org-unit-members /openapi.yaml get /v2/org-units/{orgUnitId}/members List the member IDs of an org unit, paginated. For full member profiles, look the IDs up via `GET /v2/members`. **Org-unit restriction:** If your organization restricts communication to within org units, your bot must be a member of the org unit; otherwise a 404 is returned. A bot that does not belong to any org unit cannot access any of them (every org unit returns 404). # List org units Source: https://docs.zenzap.co/api-reference/org-units/list-org-units /openapi.yaml get /v2/org-units List the org units (teams / locations) in your organization, with their members. Results are paginated. **Org-unit restriction:** If your organization restricts communication to within org units, only the org units your bot is a member of are returned. # Remove members from an org unit Source: https://docs.zenzap.co/api-reference/org-units/remove-members-from-an-org-unit /openapi.yaml delete /v2/org-units/{orgUnitId}/members Remove one or more members from an org unit. **Limits:** Maximum 5 members per request; duplicates are removed. **Behavior:** Members not in the unit are silently ignored. **Rate limit:** 1 request per minute per bot, shared with the add-members endpoint (returns 429 if exceeded). **Org-unit restriction:** If your organization restricts communication to within org units, your bot must be a member of the org unit; otherwise a 404 is returned. A bot that does not belong to any org unit cannot access any of them (every org unit returns 404). # Create a poll Source: https://docs.zenzap.co/api-reference/polls/create-a-poll /openapi.yaml post /v2/polls Create a poll in a topic. The bot must be a member of the topic. Polls are sent as messages. Each option text is stored server-side with a server-generated 6-character ID — use those IDs when submitting votes. **Anonymous polls** (`anonymous: true`) do not support voting via the API. # Delete a poll vote Source: https://docs.zenzap.co/api-reference/polls/delete-a-poll-vote /openapi.yaml delete /v2/polls/{pollId}/votes/{voteId} Retract a previously cast vote on a poll. Provide the poll ID and the vote ID returned when the vote was cast. # Vote on a poll Source: https://docs.zenzap.co/api-reference/polls/vote-on-a-poll /openapi.yaml post /v2/polls/{pollId}/votes Submit a vote on a poll on behalf of the bot. The bot must be a member of the topic the poll was posted in. Use the `id` values from the `options` array in the `POST /v2/polls` response as the `optionId`. **Constraints:** - Anonymous polls do not support voting via the API - Votes cannot be cast on closed or expired polls - Each `{pollId, optionId, voter}` combination is idempotent — re-submitting the same vote is a no-op # Rate Limits Source: https://docs.zenzap.co/api-reference/rate-limits API rate limiting information API requests are rate limited to ensure fair usage and platform stability. ## Default Limits | Scope | Limit | Window | | ----------- | -------------- | ---------- | | Per API key | 1,000 requests | 60 seconds | Rate limits are applied **per API key** (bot). Each API key you create has its own separate limit. ## Rate Limit Response When you exceed the rate limit, the API returns a `429 Too Many Requests` response. Wait until the current window resets before retrying. ## Best Practices * Implement exponential backoff when receiving 429 responses * Cache responses where possible to reduce API calls * Batch operations when the API supports it ## Custom Limits Enterprise customers may request custom rate limits for specific use cases. Contact support to discuss your requirements. Zenzap reserves the right to adjust rate limits at any time to prevent abuse or ensure platform stability. Accounts exhibiting abusive behavior may have their limits reduced or access revoked. # Setup Steps Source: https://docs.zenzap.co/api-reference/setup-steps How to set up your Zenzap API integration 1. On a Zenzap admin user, go to [https://app.zenzap.co/console](https://app.zenzap.co/console) 2. Create a new API key with the needed permissions 3. Copy the API key and secret from the API key settings The API base URL depends on your organization's **data residency**: | Data residency | API base URL | | -------------- | -------------------------- | | EU | `https://api.zenzap.co` | | US | `https://api.us.zenzap.co` | You can find your organization's data residency in the **Admin** tab under **Workspace Settings**. When creating an agent or bot, the console also provides the correct **API Base URL** alongside the credentials. 4. For each request: * Add the Authorization header with your API key * Generate a Unix timestamp in milliseconds * Calculate the HMAC-SHA256 signature: * For POST/PUT/PATCH/DELETE: Sign `{timestamp}.{body}` * For GET: Sign `{timestamp}.{uri}` (for example `/v2/members?limit=10&offset=0`) * Add `X-Signature` with the hex-encoded signature * Add `X-Timestamp` with the same timestamp used in the signature payload # Create a task Source: https://docs.zenzap.co/api-reference/tasks/create-a-task /openapi.yaml post /v2/tasks Create a task in a topic. Tasks can be assigned to specific users and have due dates. Limits: - task title is limited to 256 characters - task description is limited to 10000 characters - you can only assign tasks to members that are already in your topic - task due date must be a valid Unix timestamp in milliseconds (e.g., 1699564800000) - task externalId is limited to 100 characters # Delete a task Source: https://docs.zenzap.co/api-reference/tasks/delete-a-task /openapi.yaml delete /v2/tasks/{taskId} Delete a task by ID. # Get task details Source: https://docs.zenzap.co/api-reference/tasks/get-task-details /openapi.yaml get /v2/tasks/{taskId} Get a single task by ID. # List tasks Source: https://docs.zenzap.co/api-reference/tasks/list-tasks /openapi.yaml get /v2/tasks List tasks visible to your bot. Optional filters: - `topicId`: only tasks from a specific topic - `status`: `Open` or `Done` - `assignee`: profile ID of assignee (pass empty string to filter unassigned tasks) Pagination: - `limit`: default `50`, max `100` - `cursor`: opaque cursor from the previous response (`nextCursor`) # Update a task Source: https://docs.zenzap.co/api-reference/tasks/update-a-task /openapi.yaml patch /v2/tasks/{taskId} Update task fields. This endpoint supports partial updates. Updatable fields: - `name` (alias of `title`) - `description` - `assignee` - `dueDate` - `status` (`Open` or `Done`) Notes: - Provide either `name` or `title` (not both) - Setting `assignee` to an empty string unassigns the task - Setting `dueDate` to `0` clears the due date - When status is set to `Done`, the task is marked as closed - When `status` is provided, `topicId` is required - Every successful update creates a task system message in the topic # Add a label to a topic Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/add-a-label-to-a-topic /openapi.yaml post /v2/topics/{topicId}/labels Add a label to an existing topic. Labels are defined per organization — use `GET /v2/organization/labels` to discover the available label IDs. **Authorization:** Your API key bot must be a member of the topic (returns 404 if not). **Validation:** - `labelId` must be a valid UUID belonging to your organization (returns 400 "Invalid labelId" otherwise) - A topic can carry at most 1 label (returns 400 if the cap is exceeded) **Behavior:** Idempotent — re-adding a label already on the topic returns the current state. # Add members to a topic Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/add-members-to-a-topic /openapi.yaml post /v2/topics/{topicId}/members Add one or more members to an existing topic. **Authorization:** Your API key bot must be a member of the topic (returns 404 if not). **Validation:** - All members being added must exist and be from the same organization as the bot (returns 400 "Invalid member" if not) - Members already in the topic will result in a 400 error **Limits:** Maximum 5 members per request. Duplicate member IDs in the request are automatically removed. For adding many members, create the topic with all members upfront. # Create a topic Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/create-a-topic /openapi.yaml post /v2/topics Create a new topic with specified members. Your API key bot will automatically be added as a member. Supported request modes: - `application/json` — create a topic without a cover image - `multipart/form-data` — create a topic with an optional cover image. Send a `metaPart` (JSON topic fields, same shape as the JSON body) and an optional `filePart` (the cover image). The image is validated first: an invalid or oversized image returns 400 and no topic is created (see requirements below). A valid cover is then applied to the topic (non-square images are center-cropped); if that upload step fails afterwards the topic is still created, just without a cover. Cover image requirements (when `filePart` is provided): - must be a valid image (JPEG or PNG) - non-square images are automatically center-cropped to a square - max dimension 4096×4096; larger images return 400 - max size 8 MB; converted to JPEG automatically Limits: - topic name is limited to 64 characters - topic description is limited to 10000 characters - maximum 100 members per topic - you can only add members that are already in your organization - topic externalId is limited to 100 characters - externalId must be unique per bot - you cannot create multiple topics with the same externalId # Create organization label Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/create-organization-label /openapi.yaml post /v2/organization/labels Create a new label in your organization. The label name must be unique within the organization (case-insensitive). Use the returned label ID when adding the label to a topic via `POST /v2/topics/{topicId}/labels`. # Get topic by external ID Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/get-topic-by-external-id /openapi.yaml get /v2/topics/external/{externalId} Get details of a specific topic by its external ID (set when creating the topic). **Authorization:** Your API key bot must be a member of the topic to access its details. **Note:** For security reasons, a 404 response is returned both when the topic does not exist and when your bot is not a member of the topic. ## Deep linking to topics You can open Zenzap directly to a topic using its external ID by adding a query parameter to the Zenzap app URL. | Component | Value | |-----------|-------| | Base URL | **https://app.zenzap.co** | | Query parameter | **external_topic** | | Parameter value | Your external ID | For example, use **https://app.zenzap.co?external_topic=project-123** to link directly to a topic with external ID "project-123". This is useful for creating links from your application that navigate users directly to the relevant conversation in Zenzap. ### Short vs fully qualified external ID You can use either the **short external ID** (the value you passed when creating the topic) or the **fully qualified external ID**. For security purposes, if multiple bots create topics with the same external ID, each topic is internally prefixed with the bot ID in the format **bot_id:external_id**. The bot ID follows the format `b@` (e.g., `b@660e8400-e29b-41d4-a716-446655440003`). To avoid ambiguity, use the fully qualified format like **https://app.zenzap.co?external_topic=b@660e8400-e29b-41d4-a716-446655440003:project-123**. | Format | Example URL | |--------|-------------| | Short | **https://app.zenzap.co?external_topic=project-123** | | Fully qualified | **https://app.zenzap.co?external_topic=b@660e8400-e29b-41d4-a716-446655440003:project-123** | ### Multiple matches If you use the short external ID and multiple bots have created topics with the same external ID, the user will see a selection screen to choose the correct topic: ![Topic selection screen](/images/topic_selection.jpg) ### Platform availability Deep linking is currently supported on **Web** and **Windows** versions of Zenzap. # Get topic details Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/get-topic-details /openapi.yaml get /v2/topics/{topicId} Get details of a specific topic including its name, description, and member IDs. **Authorization:** Your API key bot must be a member of the topic to access its details. **Note:** For security reasons, a 404 response is returned both when the topic does not exist and when your bot is not a member of the topic. # Get topic messages Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/get-topic-messages /openapi.yaml get /v2/topics/{topicId}/messages Get messages from a topic with cursor-based pagination. Messages are returned in the specified order. **Authorization:** Your API key bot must be a member of the topic to access its messages. **Pagination:** Use the `cursor` parameter with the value from `nextCursor` in the response to get the next page. The `before` and `after` parameters are mutually exclusive - use one or the other, not both. **Note:** For security reasons, a 404 response is returned both when the topic does not exist and when your bot is not a member of the topic. # List organization labels Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/list-organization-labels /openapi.yaml get /v2/organization/labels List the labels available in your organization. Use the returned label IDs when adding a label to a topic via `POST /v2/topics/{topicId}/labels`. # List topics Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/list-topics /openapi.yaml get /v2/topics Get a paginated list of all topics and direct messages where your API key bot is a member. Results are sorted by creation date (newest first). Pagination: - `limit`: default `50`, max `100` - `cursor`: opaque cursor from the previous response (`nextCursor`) # Remove a label from a topic Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/remove-a-label-from-a-topic /openapi.yaml delete /v2/topics/{topicId}/labels/{labelId} Remove a label from an existing topic. **Authorization:** Your API key bot must be a member of the topic (returns 404 if not). **Behavior:** Idempotent — removing a label not present on the topic returns the current state. # Remove members from a topic Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/remove-members-from-a-topic /openapi.yaml delete /v2/topics/{topicId}/members Remove one or more members from an existing topic. **Authorization:** Your API key bot must be a member of the topic (returns 404 if not). **Behavior:** Members not currently in the topic are silently ignored. If none of the provided members are in the topic, no removal occurs and the current topic state is returned. **Limits:** Maximum 5 members per request. Duplicate member IDs are automatically removed. **Note:** The bot can remove itself from the topic if needed. # Set or update a topic cover image Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/set-or-update-a-topic-cover-image /openapi.yaml patch /v2/topics/{topicId}/cover-image Upload a cover image for an existing topic. Send the image as `multipart/form-data` with a `filePart` (the image). **Authorization:** Your API key bot must be a member of the topic (returns 404 if not). **Cover image requirements:** - must be a valid image (JPEG or PNG); undecodable images return 400 - non-square images are automatically center-cropped to a square - max dimension 4096×4096; larger images return 400 - max size 8 MB; converted to JPEG automatically **Behavior:** The image is uploaded first; the topic's cover is only updated once the upload succeeds, so a topic never references a missing image. # Update a topic Source: https://docs.zenzap.co/api-reference/topics-group-chatschannelsconversations/update-a-topic /openapi.yaml patch /v2/topics/{topicId} Update the name and/or description of a topic. **Authorization:** Your API key bot must be a member of the topic to update it. **Validation:** At least one of `name` or `description` must be provided. **Note:** For security reasons, a 404 response is returned both when the topic does not exist and when your bot is not a member of the topic. Limits: - topic name is limited to 64 characters - topic description is limited to 10000 characters # Webhook Events Source: https://docs.zenzap.co/api-reference/webhook-events Reference for webhook event payloads delivered to your endpoint When you configure a webhook, Zenzap sends HTTP POST requests to your URL when events occur. This page documents the payload structure for each event type. ## Event Envelope All webhook events share the same envelope structure: ```json theme={"theme":"github-dark"} { "id": "evt_550e8400-e29b-41d4-a716-446655440099", "type": "message.created", "eventVersion": 1, "timestamp": 1699564800000, "data": { ... } } ``` | Field | Type | Description | | -------------- | ------- | --------------------------------------- | | `id` | string | Unique event ID (use for deduplication) | | `type` | string | Event type (see below) | | `eventVersion` | integer | Schema version (currently `1`) | | `timestamp` | integer | Unix timestamp in milliseconds | | `data` | object | Event-specific payload | ## HTTP Headers Each webhook delivery includes these headers: | Header | Description | | ---------------------- | --------------------------------------- | | `X-Zenzap-Event` | Event type (e.g., `message.created`) | | `X-Zenzap-Signature` | HMAC-SHA256 signature for verification | | `X-Zenzap-Timestamp` | Unix timestamp (milliseconds) when sent | | `X-Zenzap-Delivery-Id` | Unique delivery ID (for deduplication) | ### Signature Verification When a secret is configured, verify webhooks by calculating: ```python theme={"theme":"github-dark"} import hmac import hashlib def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool: expected = hmac.new( secret.encode(), f"{timestamp}.{payload.decode()}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) ``` *** ## Message Events ### message.created Sent when a new message is posted in a topic where your bot is a member. ```json theme={"theme":"github-dark"} { "id": "evt_550e8400-e29b-41d4-a716-446655440099", "type": "message.created", "eventVersion": 1, "timestamp": 1699564800000, "data": { "message": { "id": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "senderId": "550e8400-e29b-41d4-a716-446655440001", "senderName": "Alice Johnson", "senderType": "user", "type": "text", "text": "Hello team!", "createdAt": 1699564800000 } } } ``` ### message.updated Sent when a message is edited or when voice message transcription completes. ```json theme={"theme":"github-dark"} { "id": "evt_550e8400-e29b-41d4-a716-446655440099", "type": "message.updated", "eventVersion": 1, "timestamp": 1699564850000, "data": { "message": { "id": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "senderId": "550e8400-e29b-41d4-a716-446655440001", "senderName": "Alice Johnson", "senderType": "user", "type": "text", "text": "Hello team! (edited)", "createdAt": 1699564800000, "updatedAt": 1699564850000 } } } ``` ### message.deleted Sent when a message is deleted. ```json theme={"theme":"github-dark"} { "id": "evt_550e8400-e29b-41d4-a716-446655440099", "type": "message.deleted", "eventVersion": 1, "timestamp": 1699564900000, "data": { "messageId": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "deletedBy": "550e8400-e29b-41d4-a716-446655440001" } } ``` *** ## Message Object The `message` object in message events contains: | Field | Type | Description | | ------------------- | ------- | ----------------------------------------------------------- | | `id` | string | Message UUID | | `topicId` | string | Topic UUID | | `senderId` | string | Sender ID (format: `{uuid}` for users, `b@{uuid}` for bots) | | `senderName` | string | Sender's display name | | `senderType` | string | `user`, `bot`, or `system` | | `type` | string | Message type (see below) | | `text` | string | Message text (may be empty for non-text types) | | `createdAt` | integer | Creation timestamp (ms) | | `updatedAt` | integer | Last update timestamp (ms) | | `parentId` | string | Replied-to message ID | | `mentionedProfiles` | array | Mentioned profile IDs (in mention order) | | `attachments` | array | File attachments | | `mentions` | array | User mentions | | `location` | object | Location data (for location messages) | | `task` | object | Task data (for task messages) | | `contact` | object | Contact data (for contact messages) | | `poll` | object | Poll data (for poll messages) | | `call` | object | Audio/video call data | ### Message Types | Type | Description | Additional Fields | | ---------- | ---------------------------------------------------- | -------------------------------------- | | `text` | Regular text message | `text` | | `image` | Image attachment | `attachments[]` | | `file` | File attachment | `attachments[]` | | `video` | Video attachment, including Giphy GIFs (looping MP4) | `attachments[]` | | `audio` | Voice message | `attachments[]` (with `transcription`) | | `location` | Shared location | `location` | | `task` | Task snapshot | `task` | | `contact` | Shared contact | `contact` | | `poll` | Poll | `poll` | | `call` | Audio/video call snapshot | `call` | | `admin` | System/administrative message | - | *** ## Attachments File attachments include a signed download URL: ```json theme={"theme":"github-dark"} { "attachments": [ { "id": "550e8400-e29b-41d4-a716-446655440088", "type": "image", "name": "screenshot.png", "mimeType": "image/png", "url": "https://storage.zenzap.co/attachments/...?token=...&expires=..." } ] } ``` Giphy GIFs are stored as looping MP4s. They arrive as `type: "video"` with `mimeType: "video/mp4"`, not as `image`. When the picker stored a search term or GIF title, the attachment also includes `searchQuery` (omitted when empty). **Attachment URLs expire after 60 minutes.** Download files promptly after receiving the webhook. ### Voice Message Transcription Voice messages (audio attachments) are automatically transcribed. The flow is: 1. **`message.created`** arrives with `transcription.status: "Pending"` 2. **`message.updated`** arrives when transcription completes ```json theme={"theme":"github-dark"} { "type": "message.updated", "data": { "message": { "type": "audio", "attachments": [ { "id": "550e8400-e29b-41d4-a716-446655440088", "type": "audio", "name": "voice-note.mp3", "url": "https://storage.zenzap.co/...", "transcription": { "status": "Done", "text": "Hey team, just a quick update on the project..." } } ] } } } ``` **Transcription Status Values:** | Status | Description | | --------- | --------------------------------- | | `Pending` | Queued for transcription | | `Started` | Transcription in progress | | `Done` | Complete - `text` field populated | | `Failed` | Transcription failed | *** ## Mentions To create mentions via External API message sending, use `<@profileId>` in message text. Example request text: ```text theme={"theme":"github-dark"} Hi <@550e8400-e29b-41d4-a716-446655440001>, can you review this? ``` In webhook payloads, mentions are returned under `message.mentions`. `message.mentionedProfiles` may also be present as a compact ID list. ```json theme={"theme":"github-dark"} { "message": { "text": "Hi <@550e8400-e29b-41d4-a716-446655440001>, can you review this?", "mentionedProfiles": ["550e8400-e29b-41d4-a716-446655440001"], "mentions": [ { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Alice Johnson" } ] } } ``` `message.text` keeps the mention token format (`<@profileId>`), while `mentions[]` provides resolved display names. `mentions` fields: | Field | Type | Description | | ------ | ------ | -------------------- | | `id` | string | Mentioned profile ID | | `name` | string | Mention display name | *** ## Reply Messages Messages that reply to other messages include: ```json theme={"theme":"github-dark"} { "message": { "id": "msg_new", "text": "I agree!", "parentId": "msg_original" } } ``` *** ## Location Messages ```json theme={"theme":"github-dark"} { "message": { "type": "location", "text": "", "location": { "latitude": "37.7749", "longitude": "-122.4194", "name": "San Francisco Office", "address": "123 Market St, San Francisco, CA" } } } ``` `address` is optional and is included only when available. *** ## Task Messages Task snapshots are sent when tasks are created, updated, or their status changes: ```json theme={"theme":"github-dark"} { "message": { "type": "task", "text": "", "task": { "id": "task_...", "action": "Added", "title": "Review documentation", "text": "Please review the API docs by Friday", "status": "Open", "assignee": "550e8400-e29b-41d4-a716-446655440001", "dueDate": 1699651200000, "isDueDateTimeSelected": true, "parentId": null, "subItemsCount": 0 } } } ``` **Task Actions:** | Action | Description | | ---------------- | --------------------- | | `Added` | Task created | | `Updated` | Task details changed | | `Deleted` | Task removed | | `MarkedAsDone` | Task completed | | `MarkedAsOpened` | Task reopened | | `Replied` | Comment added to task | *** ## Contact Messages ```json theme={"theme":"github-dark"} { "message": { "type": "contact", "text": "", "contact": { "name": "John Doe", "phoneNumbers": ["+1234567890"], "emails": ["john@example.com"], "role": "Engineer", "location": "San Francisco", "linkedIn": "https://linkedin.com/in/johndoe", "profileId": "550e8400-e29b-41d4-a716-446655440001" } } } ``` *** ## Reaction Events ### reaction.added ```json theme={"theme":"github-dark"} { "id": "evt_...", "type": "reaction.added", "eventVersion": 1, "timestamp": 1699564800000, "data": { "messageId": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "emoji": "👍", "userId": "550e8400-e29b-41d4-a716-446655440001", "userName": "Alice Johnson" } } ``` ### reaction.removed Same structure as `reaction.added`. *** ## Member Events ### member.added ```json theme={"theme":"github-dark"} { "id": "evt_...", "type": "member.added", "eventVersion": 1, "timestamp": 1699564800000, "data": { "topicId": "550e8400-e29b-41d4-a716-446655440000", "memberId": "550e8400-e29b-41d4-a716-446655440003", "memberName": "Carol Williams", "addedBy": "550e8400-e29b-41d4-a716-446655440001" } } ``` ### member.removed ```json theme={"theme":"github-dark"} { "id": "evt_...", "type": "member.removed", "eventVersion": 1, "timestamp": 1699564800000, "data": { "topicId": "550e8400-e29b-41d4-a716-446655440000", "memberId": "550e8400-e29b-41d4-a716-446655440003", "memberName": "Carol Williams", "removedBy": "550e8400-e29b-41d4-a716-446655440001" } } ``` *** ## Topic Events ### topic.updated Sent when a topic's name or description is changed. ```json theme={"theme":"github-dark"} { "id": "evt_...", "type": "topic.updated", "eventVersion": 1, "timestamp": 1699564800000, "data": { "topicId": "550e8400-e29b-41d4-a716-446655440000", "name": "Project Updates - Q4", "updatedBy": "550e8400-e29b-41d4-a716-446655440001" } } ``` Only changed fields are included in the payload. If only the description was updated, `name` would be absent. *** ## Poll Vote Events ### poll\_vote.created Sent when a user votes on a poll in a topic where your bot is a member. ```json theme={"theme":"github-dark"} { "id": "evt_...", "type": "poll_vote.created", "eventVersion": 1, "timestamp": 1699564800000, "data": { "pollVoteId": "550e8400_opt1_550e8400-e29b-41d4-a716-446655440001", "attachmentId": "550e8400-e29b-41d4-a716-446655440088", "messageId": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "optionId": "opt1", "voterId": "550e8400-e29b-41d4-a716-446655440001", "createdAt": 1699564800000 } } ``` ### poll\_vote.deleted Sent when a user removes their vote from a poll. ```json theme={"theme":"github-dark"} { "id": "evt_...", "type": "poll_vote.deleted", "eventVersion": 1, "timestamp": 1699564900000, "data": { "pollVoteId": "550e8400_opt1_550e8400-e29b-41d4-a716-446655440001", "attachmentId": "550e8400-e29b-41d4-a716-446655440088", "messageId": "660e8400-e29b-41d4-a716-446655440001", "topicId": "550e8400-e29b-41d4-a716-446655440000", "optionId": "opt1", "voterId": "550e8400-e29b-41d4-a716-446655440001" } } ``` *** ## Retry & Auto-Pause Behavior * Webhooks are retried up to 3 times with exponential backoff on failure * After 10 consecutive failures, the webhook is automatically paused * Re-enable a paused webhook via the [Update webhook configuration](/api-reference/webhooks/update-webhook-configuration) endpoint # Airwatch Configuration Source: https://docs.zenzap.co/enterprise/airwatch Provision Zenzap mobile app for your organization using Airwatch (UAE) The following values are supported for the Airwatch (UAE) configuration to provision Zenzap mobile app for your organization: | value name | example | type | description | | --------------------------- | :----------------------------------------: | ------- | ------------------------------------- | | auth\_DisableEmailOtpLogin | true | Boolean | Should enable/disable email OTP login | | auth\_ShouldEnforceSSO | true | Boolean | Should enforce SSO | | auth\_DisableGoogleLogin | true | Boolean | Should disable Google login | | auth\_DisableMicrosoftLogin | true | Boolean | Should disable Microsoft (OIDC) login | | auth\_SSOEmail | \ for example: `john@acme.com` | String | The email of the provisioned user | # SAML Configuration Source: https://docs.zenzap.co/enterprise/saml How to setup SAML SSO with your identity provider ## Setup SAML with Microsoft Entra ID (Azure AD) 1. Login to your Microsoft Azure portal and search for "Entra ID" in the search bar and click "Microsoft Entra ID" from the search results. Enter Entra ID 2. Click on "Enterprise applications" from the left side menu. Select Enterprise Applications 3. Click "New Application" and select "Create your own application" New Application Create Your Own 4. Enter the name of your application and click "Create" Name Your App 5. In the new app, click on "Single sign-on" from the left side menu and select "SAML" Setup Single Sign-on Select SAML 6. Click on "Edit" and enter the following values: * Identifier (Entity ID): `https://zenzap.co` * Reply URL (Assertion Consumer Service URL): `https://prod-api.zenzap.co/sso/saml/callback`\\ Basic SAML Configuration 7. Click "Save" 8. Click on "User Attributes & Claims" from the left side menu and click "Add new claim" * `displayName` - recommended to be the user first name + last name * `email` - recommended to be the user email * `id` - `user.objectid` User Attributes & Claims Configuration 9. Download the certificate (base64) and save it. Download Certificate 10. Configure SAML in Zenzap: * Go to your [Zenzap account](https://app.zenzap.co) and navigate to Admin → Organization * Under "Single sign on (SSO)", select SAML * Enter the following values from your Azure AD SAML configuration page: * **SAML SSO URL**: Format `https://login.microsoftonline.com/xxxxxx/saml2` * **Identity Provider Issuer**: Format `https://sts.windows.net/xxxx` * Upload the certificate you downloaded in step 9 * Enter **Service Provider Entity ID** as `https://zenzap.co` * Click "Save" ## User Groups (App Roles) Configure user roles to control access levels within Zenzap. 1. In Entra ID: Go to app registration → *Application Name* → App roles Configure App Roles 2. Create new app roles. Zenzap supports 2 app role values: `admin` and `user` Configure App Roles 3. Assign the app roles to the users/groups Assign App Roles ## Setup SAML with Okta 1. Login to your Okta Admin Console and go to Applications → Applications. Okta Applications 2. Click "Create App Integration" and select "SAML 2.0", then click Next. Create App Integration 3. Enter a name for your application (e.g., "Zenzap") and click Next. 4. Configure the SAML settings: * **Single sign-on URL**: `https://prod-api.zenzap.co/sso/saml/callback` * **Audience URI (SP Entity ID)**: `https://zenzap.co` * **Name ID format**: EmailAddress * **Application username**: Email Configure SAML 5. Add attribute statements: * `displayName` → `user.profile.firstName + " " + user.profile.lastName` * `email` → `user.profile.email` * `id` → `user.profile.login` Attribute Statements 6. Click Next and Finish to create the application. 7. Go to the "Sign On" tab and copy the following values: * **Sign on URL** (SAML SSO URL) * **Issuer** (Identity Provider Issuer) * Download the **Signing Certificate** Sign On Settings 8. Configure SAML in Zenzap: * Go to your [Zenzap account](https://app.zenzap.co) and navigate to Admin → Organization * Under "Single sign on (SSO)", select SAML * Enter the values from Okta: * **SAML SSO URL**: The Sign on URL from step 7 * **Identity Provider Issuer**: The Issuer from step 7 * Upload the certificate you downloaded * Enter **Service Provider Entity ID** as `https://zenzap.co` * Click "Save" Zenzap SAML Config # SAML Role Mapping Source: https://docs.zenzap.co/enterprise/saml-roles Configure automatic role assignment via SAML assertions Zenzap checks for role information in the SAML assertion to automatically assign user roles. ## Attribute Name Configure a `role` attribute in your identity provider's SAML assertion. If no role attribute is found, the user's role remains unchanged. ## Expected Values | Value | Effect | | --------------- | --------------------- | | `admin` | User gets Admin role | | `user` | User gets Member role | | (anything else) | No change | Values are case-insensitive. ## Behavior * If role attribute exists with `admin` → user becomes Admin * If role attribute exists with `user` → user becomes Member * If role attribute exists but value isn't recognized → no change * If no role attribute is present → no change # SCIM Configuration Source: https://docs.zenzap.co/enterprise/scim How to setup SCIM user provisioning with your identity provider ## Setup SCIM with Microsoft Entra ID (Azure AD) Automate user provisioning and deprovisioning between Entra ID and Zenzap. 1. In your enterprise app, click Provision User Accounts 2. Use the url `https://prod-api.zenzap.co/auth/scim` and the key provided by your CS representative. SCIM configuration 3. Test Connection and verify the connection is successful. SCIM Test Connectivity 4. Map the user attributes to the correct fields in Zenzap: | customappsso Attribute | Microsoft Entra ID Attribute | Match objects using this attribute (edit button) | Apply this mapping (edit button) | | ------------------------------------- | ------------------------------------------------------------ | :----------------------------------------------: | :------------------------------: | | userName | userPrincipalName | Yes | Always | | active | Switch(\[IsSoftDeleted], , "False", "True", "True", "False") | No | Always | | displayName | displayName | No | Only during object creation | | phoneNumbers\[type eq "mobile"].value | mobile | No | Only during object creation | SCIM Map Users 5. Set *"Sync only assigned users and groups"* and click Save SCIM Sync 6. Press start provisioning. This process typically takes up to 40 minutes. 7. Check the provision overview for any errors. SCIM Provision Overview 8. Users will appear in the Zenzap admin screen under the Organization section. Zenzap SCIM Status ## Setup SCIM with Okta Automate user provisioning and deprovisioning between Okta and Zenzap. 1. In your Okta app, go to the **General** tab and set Provisioning to "SCIM". Enable SCIM 2. Go to the **Provisioning** tab → **Integration**, and click **Edit**. 3. Configure the SCIM Connection: * **SCIM connector base URL**: `https://prod-api.zenzap.co/auth/scim` * **Unique identifier field for users**: `email` * **Supported provisioning actions**: Push New Users, Push Profile Updates, Push Groups * **Authentication Mode**: HTTP Header * **Authorization**: Bearer token provided by Zenzap support SCIM Connection Settings 4. Click **Test Connector Configuration** to verify the connection is successful. Test Success 5. Go to **To App** settings and enable: * Create Users * Update User Attributes * Deactivate Users Provisioning Options 6. Go to the **Assignments** tab and assign users or groups to the app to start provisioning. # Comprehensive Zenzap FAQ Source: https://docs.zenzap.co/faq/faq # Getting Started ## How easy is it to get started with Zenzap? Super easy! There's zero learning curve - if you can chat on your personal apps, you can use Zenzap. Download, register, and start chatting in minutes. No complex setup or training needed. ## How do I start using Zenzap? 1. Download the app or visit our website 2. Register with your phone number and email 3. Create your workspace 4. Add your team and That's it! You'll be up and running in minutes. ## Will my team need training to use Zenzap? Not at all. If your team can use standard messaging apps, they can use Zenzap. The interface is intuitive and familiar - your team can switch from their personal apps to Zenzap seamlessly, using the same familiar chat gestures and features they're used to, plus, essential work related features. ## How smooth is the transition to Zenzap? Incredibly smooth! Your team can be chatting on their personal apps one minute and doing exactly the same on Zenzap the next. No learning curve, no hassle, just simple and familiar messaging in a professional environment. ## How do I add my team? Add your team via email, phone contacts, or sharing a unique link. Team members follow simple prompts to join your workspace. You control who has access to what, and can approve any new join request. ## Can I use my Microsoft/Google account to login? Yes! We have SSO that lets you use your accounts to log in seamlessly. ## Is there a web version available? Yes! We have desktop apps for Mac and Windows, plus a web application, in addition to our mobile apps for iOS and Android. Access Zenzap however works best for you. All is synced. ## What if I need help getting set up? Our team is ready to help! You'll get a welcome message right after signing up - just reply to get assistance. We're committed to making your setup experience smooth and easy. ## Can I test Zenzap before rolling it out to my whole team? Absolutely! Start with a small group to test features and get comfortable. Once you see how easy it is, gradually add more team members. Many teams start with their core group and expand from there. We are here to answer any questions. # Control, Security & Privacy ## Is Zenzap secure for sharing sensitive company information? At Zenzap, we maintain enterprise-level security infrastructure and adhere to the highest standards of data protection compliance, including GDPR and CCPA frameworks. Our robust encryption protocols safeguard your information throughout its lifecycle. We're trusted by organizations across heavily regulated sectors, including financial institutions and healthcare providers, demonstrating our commitment to stringent data security measures. The protection and confidentiality of your data remain our foremost priority. ## Can I control who has access to my workspace? Absolutely! As an admin, you have complete control over your workspace. You decide exactly who has access to files and group conversations, manage team permissions instantly, and maintain full oversight of all activity. When team members leave, simply remove them with one click - they'll immediately lose access to everything, with nothing being saved on their devices, keeping your communication and data secure within your organization. This is true for all plans. ## Does any company information get saved on team members' devices? Company data is only cached on team members devices, to enable you continue to work even if you enter a spotty connection like the an underground metro station. All data is securely stored in the cloud with enterprise-grade encryption. Unlike personal messaging apps, when someone leaves, you just remove them and they have no way to access or keep company information. ## How quickly can I onboard new team members? Very quickly! When you add someone new, they get immediate access to their relevant chats and can see the full conversation history. This means they can get up to speed instantly without needing to be briefed on past discussions. ## What happens when a team member leaves? Simply remove them with one click - they'll immediately lose access to all conversations, files and data. Nothing remains on their device, keeping your company information secure within your workspace. All historical contributions remain in your workspace. ## Where is our data stored? We store data in multiple locations, including both EU and US servers and comply with the appropiate regional privacy laws. Our enterprise custoemrs provide manage the encryption keys controlling their data for addtional layer of privacy. ## Can I monitor workspace activity as an admin? Yes! Get insights into workspace usage, see active users, track file sharing, and monitor group creation. This helps ensure your workspace is being used effectively and securely. ## What enterprise security features are available? Enterprise plans include advanced security features like: * SAML, SCIM, and SSO integration * Enterprise encryption key management * Comprehensive audit logs * SIEM export capability * Custom security controls ## How does file security work? Pro and higher plans include advanced file scanning and security features to protect your devices and data. # Work-Life Balance & Separation ## Can I set specific working hours for notifications? Yes! Set your working hours and only receive notifications during those times. Outside those hours, notifications are off so you can focus on your personal life, and maintain healthy boundaries. Perfect for teams working across different time zones and shifts. ## Can I schedule messages to be sent later? Absolutely! Write your message anytime and schedule it to send during working hours. Great for communicating with teams in different time zones or preparing updates in advance without disturbing people outside work hours. ## How does Zenzap separate work from personal life? Unlike personal messaging apps, where work and personal chats are mixed together, Zenzap keeps your work communication completely separate. It's a dedicated workspace for your team with professional features, and nothing mixes with your personal messages. # Organization & Productivity ## How much storage do I get for files and media? Our storage options grow with your needs. The Free plan includes 1GB of secure cloud storage, perfect for getting started or for small team. The Pro plan provides 100GB of storage space which is enough for years of usage, while our Business+ plan offers unlimited storage. We automatically optimize file sizes to help you make the most of your storage space. ## How do task templates work? For teams with recurring workflows, our Pro plan and above let you save any task list as a template. Simply create your task list once, save it as a template, and reuse it anytime with a single click. It's perfect for standardizing processes, onboarding new team members, or managing repeated projects and day-to-day tasks efficiently. ## Can I break down complex tasks into smaller parts? Absolutely! With our Pro plan and above, you can break down any task into smaller, more manageable sub-tasks. This feature helps teams tackle complex projects step by step, maintain clear priorities, and ensure nothing falls through the cracks. Team members can focus on their specific components while managers maintain oversight of the bigger picture. ## How can I sort and filter tasks? Our Pro plan includes smart task management features that let you organize tasks exactly how you need them. Sort tasks by due dates to see what's urgent, filter by assignee to check workloads, or organize by priority to focus on what matters most. You can also track creation dates and status updates, making it easy to stay on top of everything your team is working on. ## Can I turn tasks into calendar events? Yes! With our Pro plan and above, you can convert any task directly into a calendar event with just one click. This seamless integration ensures all your deadlines appear in your calendar automatically. It's especially useful for time-sensitive tasks and helps keep everyone aligned with team schedules and milestones. ## What types of groups can I create? You can create dedicated spaces for any team need. Set up dedicated channels for team communication, project-specific groups for focused collaboration, announcement channels for important updates, topic-based discussions for specific subjects, and private groups for sensitive matters. Each type can be customized with its own settings and permissions to match your workflow. ## How do tasks and to-do lists work? Every chat includes a powerful built-in task management system. You can create and assign tasks to team members, set due dates and priorities, track progress with real-time status updates, add detailed checklists, set up tasks for regular work, and get automatic reminders for upcoming deadlines. Everything stays organized right where your team is already communicating. ## How do I manage team announcements? Create dedicated announcement channels where only designated team members can post updates. Everyone sees important communications in one place, and you can pin critical messages to keep them easily accessible. You can also track acknowledgments to ensure important updates are seen by the whole team - perfect for company-wide communication. ## Can I organize files and documents in a structured way? Yes! Files are automatically organized by chat and topic, making them easy to find. You can browse by file type, upload date, or create your own custom categories. Our powerful search helps you locate any file instantly, and everything stays neatly organized in your workspace's central file library. # Integrations & Connectivity ## What systems can I use Zenzap on? Zenzap works everywhere your team does. Use our mobile apps on iOS and Android for on-the-go access, desktop apps for Mac and Windows when you're at your computer, or access through any web browser. Your conversations and files stay perfectly synced across all your devices, so you're always up to date no matter how you connect. ## What calendar integrations are available? We integrate directly with both Google Calendar and Microsoft Outlook, bringing your schedule right into Zenzap. You can view all your meetings, schedule new ones, and send invites directly from any chat. The calendar integration syncs automatically, so your schedule is always up to date no matter where you update it. ## How do video meetings work with Zenzap? We make video meetings seamless through our integrations with Google Meet and Zoom, and Teams. Generate meeting links instantly in any chat conversation, and participants can join with a single click. There's no need to switch between apps or share links separately - everything happens right where your team is already communicating. ## Can I connect Zenzap with our project management tools? We're integrating with popular project management tools like Monday.com, Asana and more. Once connected, you'll see project updates and notifications directly in your Zenzap chats, and you can update tasks, change project status, and modify items right from your conversations - no need to switch apps. This two-way integration keeps everyone in sync without having to check multiple tools. We're continuously adding new integrations based on our customers' needs. ## What other business platforms can I integrate with Zenzap? We're actively expanding our integration capabilities with leading CRM systems, marketing platforms, help desk solutions, and other essential business tools. These integrations ensure your team can quickly react to important business events - whether it's a new sales opportunity, customer support issue, or marketing campaign update. When critical business data flows directly into your team conversations, everyone stays on the same page and can take immediate action. No more delays from switching between apps or waiting for updates - your team can see, discuss, and respond to important business matters in real-time, right where they're already collaborating. ## What kind of API integration options are available? Business+ and Enterprise plans include comprehensive API access to connect Zenzap with your existing systems. You can build custom workflows, automate routine tasks, enable data synchronization, and create tailored integrations that match your specific needs. Our API documentation makes it easy for your developers to get started. ## Can I build custom integrations for my organization? Enterprise customers can work with our team to build and deploy custom integrations specific to their organization's needs. We'll help create tailored solutions that fit your unique workflow, whether that's connecting to internal systems, automating processes, or building specialized features. Our development team works closely with you to understand and implement exactly what you need. # Pricing & Plans ## Is Zenzap free to use? Absolutely! Start with our Free plan that includes essential features: 1GB storage, 5 project channels, tasks, working hours, scheduled messages, calendar integration, and WhatsApp migration. Perfect for trying Zenzap or for small teams. ## How much does Zenzap cost? Zenzap is available to try for free with our Free plan. If you're ready to take it up a notch, you can choose our Most Recommended Pro plan at just £2 per user/month or our Business+ plan at £6 per user/month. If you're a large organization with specific requirements, you can request a demo for our Enterprise plan for a custom quote to fit your unique needs. ## Which plan is right for my team? It depends on your team size and needs. If you're just trying it out or have a small team, the **Free** plan is a great place to begin. If you need more storage, unlimited project channels, advanced task management tools, and file malware scanning, our **Pro** plan at £2 per user/month is the perfect fit for growing teams. If your team has reached a significant scale and needs workflows integration with your work tools such as CRM, work management, etc. or additional control, the **Business+** plan at £6 per user/month is the best for you. If you're a larger organization seeking a fully customized solution and dedicated support, then the **Enterprise** plan is the right solution. ## Can I pay monthly or annually? While we offer the best prices for annual billing, you can choose to pay monthly with the Pro and Business+ plans. We want to provide maximum flexibility for your needs. ## Do you offer any discounted plans? Yes! We offer special rates for NGOs, non-profits, educational institutions, and growing teams with unique needs. Contact us at [support@zenzap.co](mailto:support@zenzap.co) to discuss a custom package. ## How does adding new users affect billing? New team members will be added to your plan in the relevant month, and you'll be charged accordingly. You can easily upgrade or downgrade your plan anytime to manage team sizes and costs. ## How can I manage my billing? You can easily manage all billing information from your account settings, including payment details, plan changes, and billing history. Need help? Contact us at [support@zenzap.co](mailto:support@zenzap.co). ## What payment methods do you accept? You can easily pay with any credit card. For annual subscriptions, we also offer the option to be invoiced annually for your convenience. For any billing inquiries, please get in touch at [support@zenzap.co](mailto:support@zenzap.co). ## Can I change my plan? You can easily change your plan anytime through your account settings. If you'd like some guidance, or if you have specific requests, you can contact our team at [support@zenzap.co](mailto:support@zenzap.co), and we'll be happy to help. Need more help? Just reply to your welcome message in the app - we're here to help you get the most out of Zenzap! # Customer Support ## How can I get help if I need it? We offer multiple ways to get the support you need. Our in-app chat support connects you directly with our team for immediate assistance. You can also reach us via email for detailed inquiries at [support@zenzap.co](mailto:support@zenzap.co). Whatever your preferred learning style or need, we're here to help you succeed with Zenzap. ## What are your support hours? Our support team is available during regular business hours to help with any questions or issues you might have. For urgent matters, we provide 24/7 coverage to ensure your team never gets stuck. Premium plans customers receive priority support with guaranteed response times, ensuring minimal disruption to your business operations. ## Do you provide team training? While Zenzap is designed to be intuitive and easy to use, we understand that some teams benefit from additional guidance. We provide comprehensive getting started guides that walk you through all key features, detailed video tutorials for visual learners, and custom training sessions for larger teams. We provide training onboarding team sessions to help you get the most out of Zenzap, and for enterprise customers, we offer tailored training programs to match their specific needs. ## What support is included with each plan? Support levels grow with your needs. Free plan users receive basic customer support via chat and email. Pro plan customers get additional team onboarding training and 24/7 support access. Business+ users enjoy priority support with faster response times. Enterprise customers receive our highest level of support with a dedicated Customer Success Manager available 24/7, proactive account management, and customized training solutions. ## How do you handle enterprise support needs? Enterprise customers receive our most comprehensive support package. This includes a dedicated Customer Success Manager who works proactively with your team, 24/7 priority support access, customized training sessions, expert migration assistance, and best practices setup guidance. Your Success Manager becomes a trusted partner in helping your organization maximize the value of Zenzap, ensuring smooth implementation and ongoing success. # Agentic Onboarding Source: https://docs.zenzap.co/guides/agentic-onboarding How an AI agent can programmatically create a Zenzap organization, install a bot, and invite a human — all in a single API call. This endpoint allows an external AI agent to bootstrap a complete Zenzap workspace in one request: create the organization, install a bot (the agent itself), and send an invite to the human user. **Rate limit:** 1 request per minute per IP. No authentication required. ## Endpoint ``` POST https://api.zenzap.co/v2/agentic/organization/create ``` ## Request The endpoint supports two content types depending on whether you want to include a company logo. ```json JSON (no logo) theme={"theme":"github-dark"} { "companyName": "Acme Corp", "humanEmail": "founder@acme.com", "companySize": 50, "industry": "Software", "botName": "Acme Assistant" } ``` ```bash Multipart (with logo) theme={"theme":"github-dark"} curl -X POST https://api.zenzap.co/v2/agentic/organization/create \ -F 'filePart=@logo.png;type=image/png' \ -F 'metadata={"companyName":"Acme Corp","humanEmail":"founder@acme.com","companySize":50,"industry":"Software","botName":"Acme Assistant"};type=application/json' ``` For multipart requests, send two parts: | Part | Type | Description | | ---------- | ---------------- | ---------------------------------- | | `filePart` | file (image) | Company logo. Max 5 MB. Optional. | | `metadata` | application/json | JSON object with the fields below. | ## Request Fields All fields are required. | Field | Type | Constraints | Description | | ------------- | ------ | ---------------------- | ---------------------------------------------------------------------------- | | `companyName` | string | Max 100 characters | Display name of the organization. | | `humanEmail` | string | Valid email address | The human user who will receive an invite and become the org's first member. | | `companySize` | int | Positive integer (> 0) | Number of employees. Mapped to a size range. | | `industry` | string | Non-empty | Free-text industry description (e.g. "Software", "Healthcare"). | | `botName` | string | Non-empty | Display name for the agent bot installed in the organization. | ## Response **Status:** `201 Created` ```json theme={"theme":"github-dark"} { "organizationId": "067d0b2f-1ee8-49f2-bb09-e2d964c8cf6b", "botProfileId": "b@f951b968-bf80-4ee6-bbbe-6ca338f57fc6", "channelId": "1b383aef-15c2-44e2-b599-d1d40a8b286c", "humanProfileId": "a3c2e1d0-9f8b-4e7a-b6c5-d4e3f2a1b0c9", "credentials": [ { "label": "API Key", "value": "utxDAedgfFXglaLX" }, { "label": "API Secret", "value": "66AybzV7s0afLeKKqpxC7wLKCTk5d7bT" }, { "label": "Control Topic ID", "value": "1b383aef-15c2-44e2-b599-d1d40a8b286c" } ] } ``` | Field | Description | | ---------------- | --------------------------------------------------------------------------------------------------- | | `organizationId` | UUID of the newly created organization. | | `botProfileId` | The bot's identity (format: `b@`). Use as `senderId` filter when polling. | | `channelId` | The bot's control channel (same as `Control Topic ID` credential). | | `humanProfileId` | The pending profile ID of the invited human. Use this to identify the human once they join. | | `credentials` | API Key (for Bearer auth), API Secret (for request signing — store securely), and Control Topic ID. | ## Error Codes | Status | Body | Cause | | ------ | ---------------------------------------- | ------------------------------------------------------------- | | `400` | `companyName is required` | Missing or empty `companyName`. | | `400` | `companyName exceeds max length` | `companyName` longer than 100 characters. | | `400` | `invalid humanEmail` | `humanEmail` is not a valid email address. | | `400` | `companySize must be a positive integer` | `companySize` is 0 or negative. | | `400` | `industry is required` | Missing or empty `industry`. | | `400` | `botName is required` | Missing or empty `botName`. | | `400` | `Unable to create organization` | Generic conflict error (e.g. email or domain already in use). | | `429` | *(empty)* | Rate limit exceeded (1 req/min per IP). | *** ## Instructions for AI Agents This section is written for AI agents. If you're a human developer, see [Build Your Own Agent](/guides/build-your-own-agent) for a full integration walkthrough. All Zenzap API requests **must** use `https://`. Never use plain `http://` — requests over HTTP will be rejected. ### OpenClaw-Based Agents — Plugin Setup If you are an OpenClaw-based agent, ask your human to install the Zenzap plugin **before** you proceed with anything else: ```bash theme={"theme":"github-dark"} openclaw plugins install @zenzap-co/openclaw-plugin ``` Once the human confirms the plugin is installed, continue with the steps below. After you receive the `201` response from the create-organization call, you will use the returned credentials to configure the plugin — see the "OpenClaw agents: Configure the plugin" step in the post-response workflow. ### Before Calling the API — Collect from the Human Always ask the human these 4 questions before making the request. Do not assume or infer the answers: 1. **Company name** — What's the name of your company or workspace? 2. **Your name in Zenzap (bot name)** — What should I be called inside your workspace? *(This is how I'll appear to your team.)* 3. **Industry** — What industry are you in? *(e.g. Software, Healthcare, Finance — used for statistical purposes only.)* 4. **Company size** — How many employees does your company have? *(Used for statistical purposes only.)* ### After the 201 Response — What to Do Next Once you receive the `201` response, you have a live bot with credentials. Follow these steps in order: #### OpenClaw agents: Configure the plugin Skip this step if you are **not** an OpenClaw-based agent. Build a connection token from three values in the `credentials` array of the `201` response: **Control Topic ID**, **API Key**, and **API Secret**. Concatenate them separated by colons and base64-encode the result: ``` base64( :: ) ``` For example, given the sample response above: ``` base64("1b383aef-15c2-44e2-b599-d1d40a8b286c:utxDAedgfFXglaLX:66AybzV7s0afLeKKqpxC7wLKCTk5d7bT") ``` Then run: ```bash theme={"theme":"github-dark"} openclaw zenzap configure --token ``` This connects the Zenzap plugin to your newly created organization. Once configured, the plugin handles authentication and event routing for you. #### 1. Read the full API docs Load the complete Zenzap API reference into your context: ``` https://docs.zenzap.co/llms-full.txt ``` This covers all endpoints, [authentication](/api-reference/authentication) (Bearer token + HMAC-SHA256 signing), event types, [polling](/api-reference/long-polling), messaging, topics, and members. #### 2. Ask the human which topics to create **Outside of Zenzap**, ask the human which channels they'd like set up. Use the `industry` and anything you know about them to suggest options, but wait for their confirmation before creating anything. Once confirmed, create each topic: ```json theme={"theme":"github-dark"} POST https://api.zenzap.co/v2/topics { "name": "", "members": [""] } ``` The bot is automatically added as a member of every topic it creates. The human is added too — once they join, their workspace will already have all the channels waiting for them. #### 3. Start polling for events Start polling **before** telling the human to join, so you don't miss their first message: ``` GET https://api.zenzap.co/v2/updates?timeout=30 ``` Keep polling in a loop using `nextOffset` from each response. On `409`, reset the offset. Full details and a working Python example are in the [Long Polling](/api-reference/long-polling) docs. When you receive a `message.created` event from a non-bot sender, mark it as read before responding: ``` POST https://api.zenzap.co/v2/messages/{messageId}/read ``` #### 4. Tell your human to join **Outside of Zenzap**, let the human know their workspace is ready: ``` Your Zenzap workspace is all set — channels included. You should have received an invite — log in at https://app.zenzap.co to get started or download the app ony your mobile phone. ``` Listen for `message.created` events from non-bot senders — that's your signal they're active. Human profile IDs are plain UUIDs; bot IDs start with `b@`. # Build Your Own Agent Source: https://docs.zenzap.co/guides/build-your-own-agent A step-by-step guide to building a custom AI agent that integrates with Zenzap This guide walks you through building a custom AI agent that listens for messages in Zenzap and responds using OpenAI. By the end, you'll have a working bot that polls for new messages, processes them through an LLM, and sends replies back to your topics. ## Prerequisites * A Zenzap API key and secret (see [Authentication](/api-reference/authentication)) * Python 3.10+ * An [OpenAI API key](https://platform.openai.com/api-keys) ## Project Setup Create a new directory for your agent and install the required dependencies: ```bash theme={"theme":"github-dark"} mkdir zenzap-agent && cd zenzap-agent python -m venv venv && source venv/bin/activate pip install requests python-dotenv openai ``` Create a `.env` file with your credentials: ```bash theme={"theme":"github-dark"} BOT_API_KEY=your_bot_api_key_here BOT_SECRET=your_bot_secret_here CONTROL_CHANNEL_ID=your_control_channel_topic_id_here OPENAI_API_KEY=your_openai_api_key_here OPENAI_MODEL=gpt-4o API_BASE_URL= # provided when you create the agent ``` | Variable | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `BOT_API_KEY` | Your Zenzap API key (from the [console](https://app.zenzap.co/console)) | | `BOT_SECRET` | Your Zenzap API secret (used for request signing) | | `CONTROL_CHANNEL_ID` | The topic ID where the bot will post status messages (e.g. "connected", "disconnecting") | | `OPENAI_API_KEY` | Your OpenAI API key | | `OPENAI_MODEL` | The OpenAI model to use (defaults to `gpt-4o`) | | `API_BASE_URL` | The Zenzap API base URL — provided by the Zenzap console when you create the agent. It is region-specific; do not hardcode `https://api.zenzap.co`. | ## Step 1 — Build the Zenzap API Client Create `zenzap_client.py`. This module handles all communication with the Zenzap API, including HMAC request signing (see [Authentication](/api-reference/authentication) for details). Start with the response wrapper and client constructor: ```python theme={"theme":"github-dark"} import hashlib import hmac import json import time from typing import Any, Optional from dataclasses import dataclass from urllib.parse import quote, urlencode import requests @dataclass class ApiResponse: status: int data: Any success: bool @classmethod def from_response(cls, response: requests.Response) -> "ApiResponse": try: data = response.json() except ValueError: text = response.text.strip() data = {"raw": text} if text else {} return cls( status=response.status_code, data=data, success=200 <= response.status_code < 300 ) @classmethod def from_exception(cls, exception: requests.RequestException) -> "ApiResponse": return cls( status=0, data={"error": str(exception), "type": exception.__class__.__name__}, success=False, ) class ZenzapClient: def __init__( self, api_key: str, secret: str, base_url: str = "https://api.zenzap.co", timeout: float = 30.0, ): self.api_key = api_key self.secret = secret self.base_url = base_url.rstrip("/") self.timeout = timeout ``` Next, add the private methods that handle request signing and HTTP calls. Every request must include an HMAC-SHA256 signature — `GET` requests sign the URI path, while `POST`/`PATCH`/`DELETE` requests sign the JSON body: ```python theme={"theme":"github-dark"} def _generate_signature(self, data: str, timestamp: str) -> str: return hmac.new( self.secret.encode("utf-8"), f"{timestamp}.{data}".encode("utf-8"), hashlib.sha256 ).hexdigest() def _get_headers(self, signature: str, timestamp: str, include_content_type: bool = False) -> dict: headers = { "Authorization": f"Bearer {self.api_key}", "X-Signature": signature, "X-Timestamp": timestamp, } if include_content_type: headers["Content-Type"] = "application/json" return headers def _get(self, path: str) -> ApiResponse: timestamp = str(int(time.time() * 1000)) signature = self._generate_signature(path, timestamp) url = f"{self.base_url}{path}" try: response = requests.get( url, headers=self._get_headers(signature, timestamp), timeout=self.timeout, ) return ApiResponse.from_response(response) except requests.RequestException as exception: return ApiResponse.from_exception(exception) def _request_with_body(self, method: str, path: str, body: dict) -> ApiResponse: body_str = json.dumps(body, separators=(",", ":")) timestamp = str(int(time.time() * 1000)) signature = self._generate_signature(body_str, timestamp) url = f"{self.base_url}{path}" try: response = requests.request( method, url, headers=self._get_headers(signature, timestamp, include_content_type=True), data=body_str, timeout=self.timeout, ) return ApiResponse.from_response(response) except requests.RequestException as exception: return ApiResponse.from_exception(exception) def _post(self, path: str, body: dict) -> ApiResponse: return self._request_with_body("POST", path, body) def _patch(self, path: str, body: dict) -> ApiResponse: return self._request_with_body("PATCH", path, body) def _delete(self, path: str, body: dict) -> ApiResponse: return self._request_with_body("DELETE", path, body) ``` Finally, add the public API methods your agent will use: ```python theme={"theme":"github-dark"} def get_current_member(self) -> ApiResponse: return self._get("/v2/members/me") def get_topic(self, topic_id: str) -> ApiResponse: return self._get(f"/v2/topics/{topic_id}") def send_message(self, topic_id: str, text: str, external_id: Optional[str] = None) -> ApiResponse: body = {"topicId": topic_id, "text": text} if external_id: body["externalId"] = external_id return self._post("/v2/messages", body) def mark_message_read(self, message_id: str) -> ApiResponse: return self._post(f"/v2/messages/{message_id}/read", {}) def add_reaction(self, message_id: str, reaction: str) -> ApiResponse: return self._post(f"/v2/messages/{message_id}/reactions", {"reaction": reaction}) def get_updates( self, offset: Optional[str] = None, limit: int = 100, poll_timeout: int = 30, ) -> ApiResponse: params: dict[str, Any] = {"limit": limit, "timeout": poll_timeout} if offset: params["offset"] = offset query = urlencode({k: v for k, v in params.items() if v is not None}, doseq=True) path = f"/v2/updates?{query}" if query else "/v2/updates" timestamp = str(int(time.time() * 1000)) signature = self._generate_signature(path, timestamp) url = f"{self.base_url}{path}" try: response = requests.get( url, headers=self._get_headers(signature, timestamp), timeout=poll_timeout + 10, ) return ApiResponse.from_response(response) except requests.RequestException as exception: return ApiResponse.from_exception(exception) ``` The client uses additional methods like `create_topic`, `list_topics`, `create_task`, etc. See the full [API Reference](/api-reference) endpoints for everything you can do. ## Step 2 — Handle Incoming Messages Create `bot.py`. Start by defining a system prompt and a state object to track the bot's runtime data: ```python theme={"theme":"github-dark"} import os import signal import sys import time from dataclasses import dataclass, field from typing import Optional from dotenv import load_dotenv from openai import OpenAI from zenzap_client import ZenzapClient DEFAULT_SYSTEM_PROMPT = ( "You are an AI assistant embedded in Zenzap, a team messaging platform. " "You operate across multiple topics (group chats). Each message you receive " "includes the sender's name and topic name as context. " "Be concise — this is chat, not a document. Short answers win. Expand only when asked. " "Never start with filler like 'Great question!' or 'Sure!'. Just answer. " "You have no memory between topics unless explicitly told." ) @dataclass class BotState: bot_member_id: str next_offset: Optional[str] = None topic_name_cache: dict = field(default_factory=dict) conversation_histories: dict = field(default_factory=dict) running: bool = True ``` * `bot_member_id` — the bot's own user ID, so it can skip its own messages. * `next_offset` — the cursor for [long polling](/api-reference/long-polling). * `topic_name_cache` — avoids repeated API calls to resolve topic names. * `conversation_histories` — per-topic message history sent to OpenAI for context. Now add the core message handler. When a message arrives, it appends it to the topic's conversation history, calls OpenAI, and sends the reply back: ```python theme={"theme":"github-dark"} def resolve_topic_name(state: BotState, zenzap: ZenzapClient, topic_id: str) -> str: if topic_id not in state.topic_name_cache: resp = zenzap.get_topic(topic_id) state.topic_name_cache[topic_id] = resp.data.get("name", topic_id) if resp.success else topic_id return state.topic_name_cache[topic_id] def handle_chat(state: BotState, zenzap: ZenzapClient, openai_client: OpenAI, model: str, system_prompt: str, msg: dict) -> None: topic_id = msg["topicId"] text = msg.get("text", "") sender_name = msg.get("senderName", "Unknown") topic_name = resolve_topic_name(state, zenzap, topic_id) history = state.conversation_histories.setdefault(topic_id, []) history.append({"role": "user", "content": f"[from: {sender_name}, in: #{topic_name}]\n{text}"}) # Keep the last 20 messages per topic to stay within token limits if len(history) > 20: history[:] = history[-20:] try: completion = openai_client.chat.completions.create( model=model, messages=[{"role": "system", "content": system_prompt}] + history, ) reply = completion.choices[0].message.content except Exception as e: zenzap.send_message(topic_id, f"⚠️ OpenAI error: {e}") return history.append({"role": "assistant", "content": reply}) zenzap.send_message(topic_id, reply) ``` ## Step 3 — Process Updates From the Poll Loop Add a function that filters incoming updates and routes relevant ones to the chat handler. The bot should ignore its own messages and empty texts: ```python theme={"theme":"github-dark"} def handle_update(state: BotState, zenzap: ZenzapClient, openai_client: OpenAI, model: str, system_prompt: str, update: dict) -> None: if update.get("eventType") != "message.created": return msg = update.get("data", {}).get("message", {}) if not msg: return if msg.get("senderId") == state.bot_member_id: return text = msg.get("text") or "" if not text.strip(): return message_id = msg["id"] zenzap.mark_message_read(message_id) zenzap.add_reaction(message_id, "👀") handle_chat(state, zenzap, openai_client, model, system_prompt, msg) ``` Key behaviors: * Only `message.created` events are processed — see [Webhook Events](/api-reference/webhook-events) for all event types. * The bot skips its own messages by comparing `senderId` to `bot_member_id`. * Each incoming message is marked as read and given an 👀 reaction as visual feedback. ## Step 4 — Wire Up the Main Loop Finally, add the `main()` function that initializes the clients and starts the long-polling loop: ```python theme={"theme":"github-dark"} def main() -> None: load_dotenv() bot_api_key = os.getenv("BOT_API_KEY") bot_secret = os.getenv("BOT_SECRET") control_channel_id = os.getenv("CONTROL_CHANNEL_ID") openai_api_key = os.getenv("OPENAI_API_KEY") api_base_url = os.getenv("API_BASE_URL", "https://api.zenzap.co") openai_model = os.getenv("OPENAI_MODEL", "gpt-4o") for name, value in [ ("BOT_API_KEY", bot_api_key), ("BOT_SECRET", bot_secret), ("CONTROL_CHANNEL_ID", control_channel_id), ("OPENAI_API_KEY", openai_api_key), ]: if not value: print(f"Missing required environment variable: {name}") sys.exit(1) zenzap = ZenzapClient(bot_api_key, bot_secret, api_base_url) openai_client = OpenAI(api_key=openai_api_key) # Verify the bot identity me_resp = zenzap.get_current_member() if not me_resp.success: print(f"Failed to fetch bot identity: {me_resp.data}") sys.exit(1) bot_member_id = me_resp.data["id"] bot_name = me_resp.data.get("name", "Bot") # Verify the control channel is accessible control_topic_resp = zenzap.get_topic(control_channel_id) if not control_topic_resp.success: print(f"Failed to fetch control channel: {control_topic_resp.data}") sys.exit(1) control_topic_name = control_topic_resp.data.get("name", control_channel_id) state = BotState(bot_member_id=bot_member_id) state.topic_name_cache[control_channel_id] = control_topic_name # Graceful shutdown def shutdown(signum, frame) -> None: state.running = False zenzap.send_message(control_channel_id, "🛑 Bot disconnecting...") sys.exit(0) signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGTERM, shutdown) zenzap.send_message( control_channel_id, f"Agent connected successfully, this is the control channel #{control_topic_name}", ) print(f"{bot_name} connected. Listening for messages...") # Long-polling loop while state.running: resp = zenzap.get_updates(offset=state.next_offset, poll_timeout=30) if not resp.success: if resp.status == 409: state.next_offset = None else: print(f"Poll error ({resp.status}): {resp.data}") time.sleep(2) continue state.next_offset = resp.data.get("nextOffset", state.next_offset) for update in resp.data.get("updates", []): handle_update(state, zenzap, openai_client, openai_model, DEFAULT_SYSTEM_PROMPT, update) if __name__ == "__main__": main() ``` The main loop uses [long polling](/api-reference/long-polling) to efficiently wait for new events. If a `409` error occurs (offset expired), it resets and starts fresh. ## Step 5 — Run the Agent Start the bot: ```bash theme={"theme":"github-dark"} python bot.py ``` You should see output confirming the connection: ``` Bot connected. Listening for messages... ``` The bot will also send a message to your control channel confirming it's online. From here, any message sent in a topic the bot has access to will trigger an OpenAI-powered response. ## Next Steps * **Custom tools** — Extend `handle_chat` to support [function calling](https://platform.openai.com/docs/guides/function-calling) so your agent can create tasks, manage topics, or call external APIs. * **Conversation management** — Use a database instead of in-memory `conversation_histories` for persistence across restarts. ## Contact Support If you have any questions or need help, don't hesitate to contact our [Support Team](mailto:support@zenzap.com). # Polls Source: https://docs.zenzap.co/guides/polls How to create polls and record votes programmatically using the Zenzap API. Polls are posted as messages in a topic. When you create a poll, each option is stored with a server-generated 6-character ID. You use those IDs when submitting votes. ## Create a Poll ``` POST https://api.zenzap.co/v2/polls ``` The bot must be a member of the topic. A poll message is sent on behalf of the bot. ```json theme={"theme":"github-dark"} { "topicId": "550e8400-e29b-41d4-a716-446655440000", "question": "Which release should we prioritize?", "options": [ "Bug fixes", "New features", "Performance improvements" ], "selectionType": "single" } ``` **Response — `201 Created`:** ```json theme={"theme":"github-dark"} { "id": "770e8400-e29b-41d4-a716-446655440099", "topicId": "550e8400-e29b-41d4-a716-446655440000", "question": "Which release should we prioritize?", "options": [ { "id": "a1b2c3", "text": "Bug fixes" }, { "id": "d4e5f6", "text": "New features" }, { "id": "g7h8i9", "text": "Performance improvements" } ], "selectionType": "single", "status": "active", "createdAt": 1699564800000 } ``` Save the `id` (the poll ID) and the `options[].id` values — you need them to submit votes. ## Vote on a Poll ``` POST https://api.zenzap.co/v2/polls/{pollId}/votes ``` Use the poll `id` from the create response as the `pollId` path parameter, and one of the `options[].id` values as `optionId`. ```json theme={"theme":"github-dark"} { "optionId": "a1b2c3" } ``` **Response — `201 Created`:** ```json theme={"theme":"github-dark"} { "id": "770e8400-e29b-41d4-a716-446655440099_a1b2c3_b@f951b968-bf80-4ee6-bbbe-6ca338f57fc6", "attachmentId": "770e8400-e29b-41d4-a716-446655440099", "optionId": "a1b2c3", "createdAt": 1699564800000 } ``` Each `{pollId, optionId, voter}` combination is idempotent — re-submitting the same vote is a no-op. ## Delete a Vote ``` DELETE https://api.zenzap.co/v2/polls/{pollId}/votes/{voteId} ``` Use the `id` from the vote creation response as `voteId`. **Response — `204 No Content`** (empty body on success) **`404`** is returned if the vote is not found. ## Request Fields ### POST /v2/polls | Field | Type | Required | Description | | --------------- | -------------------------- | -------- | ------------------------------------------------------------------------------------------ | | `topicId` | string (UUID) | Yes | Topic to post the poll in. The bot must be a member. | | `question` | string | Yes | Poll question / title. Max 500 characters. | | `options` | string\[] | Yes | Answer option texts. Min 2, max 10. Each option max 1000 characters. | | `selectionType` | `"single"` \| `"multiple"` | Yes | Whether voters may choose one or multiple options. | | `anonymous` | boolean | No | If `true`, voter identities are hidden. Anonymous polls do not support voting via the API. | ### POST /v2/polls//votes | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------------------------------------------------------- | | `optionId` | string | Yes | The 6-character ID of the option to vote for. Use the `id` from the poll's `options` array. | ## Constraints | Constraint | Behavior | | --------------------------- | ----------------------------------------------------------------------------- | | Anonymous polls | Voting via the API is not supported (`400 anonymous polls are not supported`) | | Closed polls | Votes are rejected (`400 poll is closed`) | | Not a member | Bot must be a member of the topic (`403`) | | `selectionType: "single"` | Each voter can vote for one option | | `selectionType: "multiple"` | Each voter can vote for any number of options | | Duplicate votes | Idempotent — same `{pollId, optionId, voter}` combination is a no-op | ## Webhook Events Zenzap fires webhook events when votes are cast or retracted on polls in topics your bot is a member of: | Event | Fired when | | ------------------- | -------------------------------------------------------------------------------------------- | | `poll_vote.created` | A member votes on a poll | | `poll_vote.deleted` | A vote is retracted (by a user in the app or via `DELETE /v2/polls/{pollId}/votes/{voteId}`) | See [Webhook Events](/api-reference/webhook-events#poll-vote-events) for full payload details. # Schedule Meetings With Your Calendar Source: https://docs.zenzap.co/integrations/calendar Connect you calendar with Zenzap to schedule meetings 1. In any Zenzap chat, press the "Calendar" button. 2. In case you haven't authenticated through either Google or Microsoft, you will be prompted to do so. 3. Once authenticated, you will be able to see your calendar events. 4. Select the date and time for the meeting. 5. Click on "Create" and invite will be sent to the topic / # Google Meet Source: https://docs.zenzap.co/integrations/googlemeet Start a Google Meet meeting from Zenzap 1. In any chat, press the "Call" button. 2. Select "Google Meet". 3. If you haven't connected your Google account, you will be prompted to do so. 4. Allow Zenzap access to your Google account. 5. A new Google Meet meeting will be created and a link will be shared in the chat. # Monday.com Source: https://docs.zenzap.co/integrations/monday How to connect your Monday account with Zenzap ## Adding the App 1. Open monday.com * Log in to your monday.com account. 2. Navigate to the Apps Marketplace: * In the left-hand menu, click on "Apps". * Type "Zenzap" into the search bar. 3. Install Zenzap: * Select Zenzap from the search results * Click "Install" to add Zenzap to your monday workspace. 4. Authorize the Integration: * Follow the on-screen instructions to authorize Zenzap to access your monday boards. * Choose which boards you want to integrate with Zenzap. 5. Select the topic for updates: * Select the topic you want the Monday updates to be written to ## Usage Zenzap will begin sending automated messages to the designated topic based on updates and activity from your monday boards. ## Note The integration is limited to notifications only. ## Removing the App 1. Open monday.com • Log in to your monday.com account 2. Navigate to the Apps Section • Go to the "Apps" section from the left-hand menu. 3. Find Installed Apps • Click on the Installed Apps tab. 4. Locate Zenzap 5. Remove the App: • Click on Zenzap and select "Uninstall". • Confirm the uninstallation, and Zenzap will be removed from your monday workspace. ## Contact support If you have any questions or need help, don't hesitate to contact our [Support Team](mailto:support@zenzap.com). # OpenClaw Source: https://docs.zenzap.co/integrations/openclaw How to connect your OpenClaw AI agent with Zenzap ## Prerequisites * A Zenzap account. If you don't have one yet, download the app from your iOS / Android app store and go through the onboarding process. * An OpenClaw machine with the `openclaw` CLI installed and running. ## Adding the Integration 1. Install the Zenzap plugin on your OpenClaw machine: * Open a terminal on your OpenClaw machine. * Run the following command: ```bash theme={"theme":"github-dark"} openclaw plugins install @zenzap-co/openclaw-plugin ``` 2. Connect your AI agent in Zenzap: * Open the Zenzap app and navigate to **My Apps**. * Click **Connect AI Agent**. * Select **OpenClaw** from the list of available agents. * Follow the setup wizard to configure the connection. * At the end of the wizard, copy the generated token — you will need it in the next step. 3. Configure the token on your OpenClaw machine: * Back in your terminal, run: ```bash theme={"theme":"github-dark"} openclaw zenzap setup ``` * When prompted, select **Token** as the authentication method. * Paste the token you copied from the Zenzap wizard. 4. Restart the OpenClaw gateway: * Run the following command to apply the changes: ```bash theme={"theme":"github-dark"} openclaw-gateway restart ``` ## Usage Once the setup is complete, your OpenClaw AI agent will be connected to Zenzap and ready to use within your conversations. ## Contact support If you have any questions or need help, don't hesitate to contact our [Support Team](mailto:support@zenzap.com). # Microsoft Teams Source: https://docs.zenzap.co/integrations/teams Start a Microsoft Teams meeting from Zenzap 1. In any chat, press the "Call" button. 2. Select "Microsoft Teams". 3. If you haven't connected your Teams account, you will be prompted to do so. 4. Allow Zenzap access to your Tams account. 5. A new Microsoft Teams meeting will be created and a link will be shared in the chat. # Whatsapp Chat Import Source: https://docs.zenzap.co/integrations/whatsapp How to import a WhatsApp chat into Zenzap ## iOS 1. Open WhatsApp on your iPhone. 2. Open the chat you want to export. 3. Tap the contact's name or group subject at the top of the chat window. 4. Scroll down and tap "Export Chat". 5. Choose whether to include media or not. 6. Select Zenzap from the share sheet. 7. Wait until the chat is compressed and uploaded into Zenzap. It might take a few minutes depending on the chat size. 8. You can always minimize the app and come back later to check the progress. 9. Once the chat is uploaded, you will receive a notification in Zenzap with a link to view the chat. 10. If there are any Whatsapp chat participants that are not Zenzap users, you will be prompted to invite them to Zenzap. ## Android 1. Open WhatsApp on your Android device. 2. Open the chat you want to export. 3. Tap the three dots in the top right corner. 4. Tap "More". 5. Tap "Export Chat". 6. Choose whether to include media or not. 7. Select Zenzap from the share sheet. 8. Wait until the chat is compressed and uploaded into Zenzap. It might take a few minutes depending on the chat size. 9. You can always minimize the app and come back later to check the progress. 10. Once the chat is uploaded, you will receive a notification in Zenzap with a link to view the chat. 11. If there are any Whatsapp chat participants that are not Zenzap users, you will be prompted to invite them to Zenzap. # Zoom Integration Source: https://docs.zenzap.co/integrations/zoom How to start a Zoom meeting from Zenzap ## Adding the App 1. Enter any chat. 2. Press the "Call" button. 3. Select "Zoom". 4. Enter your Zoom credentials. 5. Allow Zenzap access to your Zoom data. ## Usage 1. In any chat, press the call button and choose Zoom. 2. A new zoom meeting will be created and a link will be shared in the chat. ## Removing the App 1. Login to your Zoom Account and navigate to the Zoom App Marketplace. 2. Click Manage >> Added Apps or search for the "Zenzap" app. 3. Click the "Zenzap" app. 4. Click Remove. ## Contact support If you have any questions or need help, don't hesitate to contact our [Support Team](mailto:support@zenzap.com). # Welcome to Zenzap Source: https://docs.zenzap.co/quickstart Configure your zenzap system and find answers to your questions ## What is Zenzap? Zenzap is a powerful chat platform that allow you to manage your business. Using Zenzap you can connect with your team and collaborate on projects. You can create topics, chat with your team, share files and create tasks. Zenzap also integrates with other tools like Google Calendar, Microsoft Teams, and Zoom to make it easier to schedule meetings and collaborate with your team. ## Getting Started To get started with Zenzap, you need to create an account. You can sign up for a free account by visiting the [Zenzap website](https://app.zenzap.co) or download the Zenzap app from the [Google Play Store](https://play.google.com/store/apps/details?id=ws.loops.app) or the [Apple App Store](https://apps.apple.com/us/app/zenzap/id1624683206). Once you have created an account, you can start using Zenzap to chat with your team, create topics, and share files.