API Reference

Reference for the core Disqua REST API. Base URL: https://api.disqua.com/v1

Conventions

  • Requests and responses are JSON. Field names are snake_case (display_name, thread_id).
  • Almost every resource lives under its workspace: /v1/workspaces/:workspaceId/…. IDs are UUIDs.
  • Responses use the envelope { "ok": true, "data": { … } }; errors { "ok": false, "error": { "code", "message", "status" } }.
  • Lists are cursor-paginated with cursor + limit.
  • The machine-readable OpenAPI 3.0 spec is at api.disqua.com/api-docs (JSON: /api-docs/json). Not every route has request/response schemas yet.

Authentication

Send a bearer token on every request: Authorization: Bearer <token>. Disqua accepts three kinds of token:

TokenHow you get itUse for
JWT access tokenPOST /v1/auth/login → access_token (valid 15 minutes) + refresh_token (7 days)Your own scripts acting as you.
dq_usr_… → dq_ai_…Create a master token in the console under Profile → AI access, then exchange it with POST /v1/ai/request-session-key for a session key scoped to one workspace.AI agents and automation (Claude Code, custom bots). The master token itself is only accepted by the exchange endpoint.
dq_oat_…OAuth 2.1 authorization code flow with PKCE. Discovery: https://api.disqua.com/.well-known/oauth-authorization-server; token endpoint POST /v1/oauth/token; dynamic client registration POST /v1/oauth/register.Third-party apps and MCP clients (Claude, ChatGPT). Scopes such as channels:read, messages:read, helpdesk:write.

There are no separate per-workspace API keys yet. For server-to-server posting into a channel, use an incoming webhook.

POST /v1/auth/register

Create an account. Sends a verification e-mail.

{
  "display_name": "Jane Doe",       // 2–100 chars
  "email": "jane@example.com",
  "username": "janedoe",            // 3–50 chars, lowercase letters, digits, _
  "password": "s3cretpass",         // 8–128 chars, at least one letter and one digit
  "locale": "en"                    // optional
}

// 201 → { "ok": true, "data": { "user": { … }, "access_token": "…", "refresh_token": "…" } }
POST /v1/auth/login

E-mail + password. Accounts with two-factor authentication get { "requires_2fa": true, "temp_token": "…" } instead and finish with POST /v1/auth/2fa/challenge (temp_token, code).

{ "email": "jane@example.com", "password": "…" }

// 200 → { "ok": true, "data": { "user": { … }, "workspace": { … }, "access_token": "eyJ…", "refresh_token": "…" } }
POST /v1/auth/refresh

Exchange a refresh token for a new pair. Body { "refresh_token": "…" } (browsers send the cookie instead). Refresh tokens rotate on every use.

POST /v1/auth/logout Auth required

End the current session.

Workspaces

MethodEndpointDescription
GET/v1/workspacesWorkspaces you belong to. Optional ?slug= (exact) or ?q= (substring) filter.
POST/v1/workspacesCreate a workspace. Body: name, slug.
GET/v1/workspaces/:workspaceIdWorkspace detail.
PATCH/v1/workspaces/:workspaceIdUpdate name, description, icon (admin).
GET/v1/workspaces/:workspaceId/membersList members.
PATCH/v1/workspaces/:workspaceId/members/:userIdChange a member role (admin).
DELETE/v1/workspaces/:workspaceId/members/:userIdRemove a member (admin).
POST/v1/workspaces/:workspaceId/members/grantOffer an existing Disqua user access. Body: user_id, role (admin|member). The user accepts it in the console.
GET/v1/workspaces/:workspaceId/invitesList invites (admin).
POST/v1/workspaces/:workspaceId/invitesCreate an invite (admin). Sends an e-mail when an address is given.

Channels

MethodEndpointDescription
GET/v1/workspaces/:workspaceId/channelsChannels visible to you.
POST/v1/workspaces/:workspaceId/channelsCreate a channel. Body: name, type (public|private), description, initial_member_ids.
POST/v1/workspaces/:workspaceId/channels/dmOpen (or reuse) a 1:1 direct message. Body: user_id.
GET/v1/workspaces/:workspaceId/channels/:channelIdChannel detail.
PATCH/v1/workspaces/:workspaceId/channels/:channelIdUpdate name, topic, description.
DELETE/v1/workspaces/:workspaceId/channels/:channelIdArchive the channel.
GET/v1/workspaces/:workspaceId/channels/:channelId/membersChannel members.
POST/v1/workspaces/:workspaceId/channels/:channelId/membersJoin, or add a user. Body: user_id (omit to join yourself).
DELETE/v1/workspaces/:workspaceId/channels/:channelId/members/:userIdRemove a member / leave.
GET/v1/workspaces/:workspaceId/channels/:channelId/pinsPinned messages.
POST/v1/workspaces/:workspaceId/channels/:channelId/pinsPin a message. Body: message_id.
DELETE/v1/workspaces/:workspaceId/channels/:channelId/pins/:messageIdUnpin.

Messages

MethodEndpointDescription
GET/v1/workspaces/:workspaceId/channels/:channelId/messagesNewest first. Query: limit (max 100), cursor (created_at of the oldest message you have). Opening the channel this way marks it read for you; pass mark_read=false to only read.
POST/v1/workspaces/:workspaceId/channels/:channelId/messagesSend. Body: content (markdown, max 40,000 chars), thread_id, attachment_ids, scheduled_at.
GET/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageIdOne message.
PATCH/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageIdEdit content (author only).
DELETE/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageIdDelete (author or admin).
POST/v1/workspaces/:workspaceId/channels/:channelId/mark-unreadMark the channel unread from a given message. Body: message_id.

Message object (abridged)

{
  "id": "8b1d…",
  "channel_id": "7a0c…",
  "user_id": "7f9e…",
  "content": "Hello **world**!",
  "content_html": "<p>Hello <strong>world</strong>!</p>",
  "type": "text",                      // text | system | file | ai
  "thread_id": null,
  "reply_count": 3,
  "reactions": [ … ],
  "attachments": [ … ],
  "edited_at": null,
  "created_at": "2026-10-16 09:45:00.000"
}

Threads

MethodEndpointDescription
GET/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageId/threadReplies to a parent message.
POST/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageId/threadReply in the thread. Body: content.
GET/v1/workspaces/:workspaceId/threadsThreads you take part in.

Reactions

MethodEndpointDescription
POST/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageId/reactionsAdd a reaction. Body: emoji.
DELETE/v1/workspaces/:workspaceId/channels/:channelId/messages/:messageId/reactions/:emojiRemove your reaction.

Users

MethodEndpointDescription
GET/v1/users/meYour profile.
PUT/v1/users/meUpdate display name, avatar, timezone, locale.
PUT/v1/users/me/statusSet presence. Body: status (online|away|dnd|offline).
GET/v1/users/me/preferencesNotification and theme preferences.
PUT/v1/users/me/preferencesUpdate preferences.

Files

MethodEndpointDescription
POST/v1/workspaces/:workspaceId/filesUpload one file as multipart/form-data (field: file). Size limit by plan: 10 MB Free, 50 MB Pro, 100 MB Business and Suite.
GET/v1/workspaces/:workspaceId/filesFiles in the workspace.
DELETE/v1/files/:fileIdDelete (uploader or admin).

Notifications

MethodEndpointDescription
GET/v1/workspaces/:workspaceId/notificationsYour notifications in a workspace.
GET/v1/workspaces/:workspaceId/notifications/unread-countUnread count.
PUT/v1/notifications/:notificationId/readMark one read.
PUT/v1/workspaces/:workspaceId/notifications/read-allMark all read.

Errors & rate limits

Error codes are stable strings prefixed with E_, for example E_AUTH_TOKEN_EXPIRED (401), E_AUTHZ_NOT_MEMBER (403), E_CHANNEL_NOT_FOUND (404), E_VALIDATION_FAILED (400/422), E_PLAN_FEATURE_NOT_AVAILABLE (402). Branch on error.code, not on the message text.

Default rate limits: 600 requests per minute per authenticated user, 1,200 per minute per IP address for unauthenticated calls. Some endpoints (sending messages, recharges, exports, AI) have lower limits of their own. Over the limit you get 429 — wait and retry with backoff.