Workspace setup & adapter API

Connect your backend to Takechat using the adapter protocol.

Download OpenAPI 3.1
Implementation requirements and the full OpenAPI contract.

Connect a workspace

  1. Implement the adapter endpoints below in your backend.
  2. Issue an operator access key scoped to the workspace.
  3. In Takechat, select Add workspace and enter the endpoint URL and access key.

Takechat checks GET /info and uses the workspace name returned by the adapter. Keys are stored in macOS Keychain. This website does not collect credentials or relay customer data.

Implement an adapter

Expose the following endpoints under one base URL. Authenticate every request with Authorization: Bearer <access-key>. The adapter provides workspace identity, conversations, permissions and AI instructions. Your backend owns storage and message delivery.

GET/infoIdentity, capabilities and AI guidance
GET/conversationsUp to 100 per page, most recent or highest priority first
GET/conversations/:id/messagesNewest 500 messages, oldest first, with reply permissions
PATCH/conversations/:idSet state to open or closed
POST/conversations/:id/messagesSend a text reply to an existing conversation
01 / Connect, authenticate and discover capabilities

All endpoints are relative to your configured base URL. A bare domain expands to /api/desk/v1. Each mount represents one workspace. Every call authenticates a revocable operator key and authorizes access to that workspace. Never trust a client-supplied workspace ID as permission.

HTTPS is required; development builds allow loopback HTTP. Redirects are rejected. Responses use Cache-Control: no-store. No visitor, widget or iframe API is required.

GET /info
Authorization: Bearer <operator-access-key>

{
  "protocolVersion": 1,
  "workspace": { "id": "alpha", "name": "Alpha Studio" },
  "operator": { "id": "operator-1", "name": "Alex" },
  "capabilities": {
    "setState": true, "sendMessage": true,
    "paging": true, "priority": true
  },
  "ai": {
    "instructions": "Help our team draft accurate replies.",
    "channels": { "email": "Write concise, friendly emails." }
  }
}

The workspace name comes from /info. Changes to workspace or operator IDs require reconnection. ai is optional: omit it when you have no guidance for the operator’s AI. Its channels keys are conversation channel values. Local tool access is an operator setting, not a server capability. Missing sendMessage means false; legacy embed metadata is ignored.

02 / Read conversations and messages

IDs are opaque non-empty strings up to 300 characters. Dates are ISO 8601. Contact fields may be null; unknown channels stay visible. country is an ISO 3166-1 alpha-2 code. Return conversations by lastMessageAt, newest first, then id, at most 100 per page. lastMessageAt is the last message’s time; closing a conversation does not move it. Optional q searches the full store by name/email substring or normalized phone digits before paging. The initial app has no search UI.

Paging is optional: advertise capabilities.paging, accept state=open|closed, limit and cursor, and return nextCursor (null on the last page). Apply state before limit, like q. Use a keyset cursor over the sort key, not an offset, so pages hold steady while conversations arrive.

Priority is optional too: advertise capabilities.priority, give each conversation a priority from 0 (nothing to do) to 1 (act now), scored on the server whenever a message arrives, and accept sort=priority (highest first, unscored last). Takechat lists conversations of 0.625 or more first, and the rest by recent activity.

{
  "conversations": [{
    "id": "thread-1", "channel": "email",
    "visitorId": "sam@example.com",
    "customerId": "example.com:cus_8f3k2",
    "source": { "id": "kopicorner", "name": "Kopi Corner" },
    "contactName": "Sam",
    "contactEmail": "sam@example.com", "contactPhone": null,
    "country": "SG", "startedAt": "2026-09-20T08:00:00Z",
    "lastMessageAt": "2026-09-20T08:00:00Z",
    "updatedAt": "2026-09-20T08:05:00Z",
    "lastRole": "user", "lastText": "Can you help?", "state": "open",
    "priority": 0.75
  }],
  "nextCursor": "eyJrIjpbMC43NSwiMjAyNi0wOS0yMCJdfQ"
}

Optional customerId is a global customer ID written as issuer-domain:opaque under a domain you control. Give the same person the same value in every workspace you serve and never reassign it. Do not build it from email, phone or name. Use null when the person is not verified.

Optional source names the store, inbox or brand a conversation came from when one workspace mixes several: a stable id for grouping and a name operators see. Optional updatedAt changes on every new message, state change, delivery status change or rescoring, so Takechat re-reads only conversations that changed.

{
  "messages": [{
    "id": "message-1", "role": "user", "text": "Can you help?",
    "createdAt": "2026-09-20T08:00:00Z",
    "fallback": false, "deliveryFailed": false
  }],
  "truncated": false,
  "reply": { "allowed": true, "reason": null, "maxLength": 8000 }
}

Return at most 500 messages: the newest, oldest first, with truncated set when older ones were left out. fallback marks text that stands in for a tool call, media or other non-text content. Write it for a person: what happened and its result, with a generated image or file as a Markdown link to its HTTPS URL, never the tool’s input JSON. Business text renders as Markdown without images; customer text as plain text. When deliveryStatus is present, deliveryFailed equals deliveryStatus “failed”.

Missing reply metadata disables sending. maxLength is 1–8,000 UTF-16 code units; use a lower channel limit where required. Closing or reopening uses PATCH with {"state":"closed"} or {"state":"open"}. State changes are idempotent. Closed conversations must be reopened before a new reply, and reopen on their own when the customer writes again.

03 / Send safely, retry without duplicates
POST /conversations/thread-1/messages
Authorization: Bearer <operator-access-key>
Content-Type: application/json

{
  "clientMessageId": "2c9b4e1c-438b-4cdb-8b01-b3e521e867c6",
  "text": "Absolutely. How can I help?"
}

// Response: persisted canonical message
{
  "message": {
    "id": "message-2", "role": "assistant",
    "text": "Absolutely. How can I help?",
    "createdAt": "2026-09-20T08:01:00Z",
    "fallback": false, "deliveryFailed": false,
    "clientMessageId": "2c9b4e1c-438b-4cdb-8b01-b3e521e867c6",
    "operator": { "id": "operator-1", "name": "Alex" },
    "deliveryStatus": "sent"
  }
}

Only existing conversations and non-empty text are supported. The adapter resolves the recipient and channel, rechecks permission and channel constraints, and persists the canonical message and conversation preview. The operator identity comes from authentication, never from the request body.

Persist a unique reservation for (workspace, conversation, clientMessageId) atomically before external delivery. Concurrent requests with the same ID and text return the same outcome; changed text returns 409. Use provider idempotency or an outbox with reconciliation. Never resend an ambiguous provider outcome blindly. A timeout is not proof of failure.

Return the canonical request on replay even if the conversation was subsequently closed, provided the operator remains authorized. Keep deduplication records for the lifetime of the conversation. Polling must expose clientMessageId so the app can reconcile a response lost in transit. Provider integration and recovery are the adapter implementer’s responsibility.

pending: persisted, awaiting transport. sent: provider accepted, not proof of receipt. delivered: confirmed delivery. failed: confirmed failure. unknown: outcome not yet known. Preserve deliveryFailed for legacy messages. Legacy messages without a delivery status must not acquire an invented delivery receipt.

04 / Errors, security and compatibility

Errors use {"error":"...","code":"..."}. code is an optional reason clients can branch on, such as conversation_closed, channel_restricted, idempotency_conflict or rate_limited; unknown codes fall back to the status: 400 invalid input, 401 rejected credentials, 403 insufficient permission, 404 missing resource, 409 state/idempotency conflict, 413 oversized body, 429 throttled, 503 unavailable or invalid backend output. Never return an empty inbox for a database failure or expose provider credentials in errors.

Keep the v1 read contract compatible. New sending fields are optional for older adapters. Operator AI instructions augment a fixed application role; they do not authorize tools or sending. Automatic summaries run without tools. Local tools share the operator’s existing Claude account and are not sandboxed per workspace.

Implement the protocol in your own backend using the OpenAPI contract above. No access to the Takechat source code is required.