Quincer AI Docs Home Support Start free

Connect

API & webhooks

Quincer AI is API-first. Everything you can do in the dashboard — take over a live chat, update a lead, read the activity log — you can do via REST. This page is the map. For request/response previews against your real workspace, open Developer APIs inside the dashboard.

Authentication

All API requests use a workspace developer key. Generate one under Dashboard → Integrations → API keys, choose scopes, and pass it as a Bearer token:

Authorization: Bearer cw_dev_<your-key>

Two kinds of credentials exist: widget keys (cw_live_) are embedded in the page script and only identify a widget to the public chat endpoint. Developer keys (cw_dev_) are scoped to an organization, carry explicit scopes like leads:read or conversations:takeover, and must stay server-side.

Base URL

https://chat.quincer.com
i

Embedding Quincer in a mobile app? Use @quincer/react-native instead of calling these REST endpoints directly. The SDK wraps /api/chat, the voice-relay, the widget-key conversation endpoints, and the live agent-takeover SSE stream — with native UI, native voice, and AsyncStorage persistence. See the React Native SDK guide.

Conversations

Use these to let an outside AI (OpenClaw, a custom agent, or your own backend) discover, take over, reply on, and hand back live conversations. The takeover/reply/handback trio is how the OpenClaw integration works under the hood.

MethodPathScopePurpose
GET/api/v1/conversationsconversations:readList conversations. Filters: widget_id, status (single or comma-separated), channel, engaged (true|all), updated_since. Cursor-paginated.
GET/api/v1/conversations/:idconversations:readFetch full transcript + metadata.
POST/api/v1/conversations/:id/takeoverconversations:takeoverClaim a conversation — AI stops responding.
POST/api/v1/conversations/:id/replyconversations:replyPost a human-agent message (auto-translated to visitor's language).
POST/api/v1/conversations/:id/handbackconversations:handbackRelease the takeover — AI resumes.
POST/api/v1/conversations/:id/handoffconversations:writeSwitch the active persona for this conversation. Body: { "persona_id": "..." | null } (null clears the override).
POST/api/v1/conversations/:id/reply-templateconversations:writeSend a pre-approved WhatsApp template outside the 24-h window. Requires the conversation to be taken over first (same precondition as /reply). Body: { "template_id", "variables": [], "speaker_name"? }.
GET/api/v1/conversations/:id/whatsapp-windowconversations:readWhatsApp 24-h customer-service window state. WhatsApp channel only.

Leads

MethodPathScopePurpose
GET/api/v1/leadsleads:readList leads. Filters: widget_id, stage, temperature, assigned_to, updated_since. Cursor-paginated — see Pagination below.
POST/api/v1/leadsleads:writeCreate a lead outside the chat flow (e.g. CRM ingestion, marketing-form capture). Requires widget_id and at least one of email, name, phone.
GET/api/v1/leads/:idleads:readFull lead record (BANT, tags, assigned task history).
POST/api/v1/leads/:idleads:writeUpdate fields: stage, temperature, tags, score, assignment, contact info. PATCH on the same path is also accepted (legacy alias).
DELETE/api/v1/leads/:idleads:deletePermanently delete the lead and its associated rows.
GET/api/v1/leads/:id/emailsleads:readList drafted & sent follow-up emails for this lead.
POST/api/v1/leads/:id/emails/generateleads:writeGenerate an AI follow-up draft on demand. Reuses the latest draft if one already exists.
PATCH/api/v1/leads/:id/emails/:email_idleads:writeInline-edit a draft's subject/body. Draft status only.
POST/api/v1/leads/:id/emails/:email_idleads:writeSend a draft. Body: { "action": "approve" | "send" }.
POST/api/v1/leads/:id/emails/:email_id/redraftleads:writeAI rewrites the existing draft per natural-language instructions. Body: { "instructions": "..." }.

Tasks

MethodPathScopePurpose
GET/api/v1/taskstasks:readAgent task log. Filters: widget_id, lead_id, status, type, updated_since. Mounted at /api/v1/activities too as a legacy alias.
POST/api/v1/taskstasks:writeSchedule a new task. Requires widget_id, type, title.
GET/api/v1/tasks/:idtasks:readFull task record with execution + result data. Mounted at /api/v1/activities/:id too as a legacy alias.
POST/api/v1/tasks/:idtasks:writeUpdate status, priority, assignment, or schedule. PATCH also accepted.
DELETE/api/v1/tasks/:idtasks:deleteCancel/delete the task. Useful for clearing stuck tasks that no longer have a runner.

Knowledge base

MethodPathScopePurpose
GET/api/v1/knowledgeknowledge:readList knowledge items. Filters: widget_id, persona_id, source_type, query (or q) to search title/content/URL. Cursor-paginated.
POST/api/v1/knowledgeknowledge:writeCreate a knowledge item. Body: { "title", "content", "widget_id"?, "persona_id"?, "personas"?: [] }. Embedding is regenerated automatically.
GET/api/v1/knowledge/:idknowledge:readSingle item.
POST/api/v1/knowledge/:idknowledge:writeUpdate title/content/persona binding. PATCH also accepted.
DELETE/api/v1/knowledge/:idknowledge:deleteCascade-deletes chunks and embeddings.

Personas

MethodPathScopePurpose
GET/api/v1/personaspersonas:readList personas. Filter: widget_id.
POST/api/v1/personaspersonas:writeCreate a persona. Body: { "name", "systemPrompt", "widget_id"?, "bubble_color"?, "url_patterns"?, "is_default"? }.
GET/api/v1/personas/:idpersonas:readSingle persona.
POST/api/v1/personas/:idpersonas:writeUpdate fields: name, prompt, urlPatterns, isDefault, voice, agentMode, Slack/Telegram routing, Meta asset bindings, enabledToolIds, email-inbox settings.
DELETE/api/v1/personas/:idpersonas:deleteDelete. Refuses if this is the widget's last persona.

Sales playbook

MethodPathScopePurpose
GET/api/v1/playbookplaybook:readList entries. Filters: widget_id, category.
POST/api/v1/playbookplaybook:writeCreate. category must be one of value_prop, roi_data, objection_handler, competitor_comparison, pricing, case_study.
GET/api/v1/playbook/:idplaybook:readSingle entry.
POST/api/v1/playbook/:idplaybook:writeUpdate. PATCH also accepted.
DELETE/api/v1/playbook/:idplaybook:deleteDelete.
POST/api/v1/playbook/generateplaybook:writeUpload a document (PDF / Markdown / text, max 10MB) via multipart/form-data to auto-generate entries (mode=generate) or import as-is (mode=import). Requires file, widgetId.

Models

MethodPathScopePurpose
GET/api/v1/modelswidgets:readList the AI models available to the workspace (from its configured provider keys). Useful for picking a widget's aiModel. Cached 10 minutes.

Widgets

MethodPathScopePurpose
GET/api/v1/widgetswidgets:readList widgets in the workspace.
POST/api/v1/widgetswidgets:writeCreate a new widget (one brand / deploy target). Requires brand; websiteUrl records the site it's deployed on. Seeds default personas. By default also mints a public embed key (cw_live_*) and returns apiKey + embed (scriptUrl + ready-to-paste snippet), so one call yields a deployable brand. Pass withApiKey: false to skip.
GET/api/v1/widgets/:idwidgets:readFull widget config: branding, AI model, agent mode, voice, languages, attendees.
POST/api/v1/widgets/:idwidgets:writeUpdate any configurable field. Plan-gated knobs (proactive mode, multi-domain, voice, attachments) are enforced. PATCH also accepted.
DELETE/api/v1/widgets/:idwidgets:writeDelete. Refuses if this is the workspace's only widget.
GET/api/v1/widgets/:id/keyswidgets:readList the widget's active public embed keys, each with an embed snippet. Re-fetch a key returned at create time.
POST/api/v1/widgets/:id/keyswidgets:writeMint a new public embed key for the widget (rotation). Returns the key + embed snippet.
POST/api/v1/widgets/:id/import-storefrontknowledge:writeImport a public Shopify storefront's catalog (its /products.json) into this widget's knowledge base — no app install needed. Body: { url? } (defaults to the widget's websiteUrl). Products are also available live to the generative-UI product panel.

Integrations

MethodPathScopePurpose
GET/api/v1/integrationsintegrations:readList supported integrations and their connection state for this workspace (optionally a specific widget_id).
GET/api/v1/integrations/:providerintegrations:readConnection details for one provider. Returns { "connected": false } if not connected.

Connect/disconnect flows still require an interactive OAuth roundtrip and live under the dashboard at Dashboard → Integrations. This endpoint is read-only by design — programmatically minting OAuth grants from a dev key is a footgun.

Webhooks

Programmatic webhook subscription management. Previously dashboard-only; now a developer key with webhooks:manage can provision a subscription end-to-end.

MethodPathScopePurpose
GET/api/v1/webhookswebhooks:manageList subscriptions in this workspace.
POST/api/v1/webhookswebhooks:manageCreate. Body: { "url", "events": [] }. The signing secret is returned once on creation — store it; subsequent GETs do not include it.
GET/api/v1/webhooks/:idwebhooks:manageSingle subscription.
POST/api/v1/webhooks/:idwebhooks:manageUpdate url, events, active state, or reset_fail_count. PATCH also accepted.
DELETE/api/v1/webhooks/:idwebhooks:manageDelete the subscription.

Voice

MethodPathScopePurpose
GET/api/v1/voice/sessionsvoice:readList voice sessions. Filters: widget_id, status. Cursor-paginated by started_at.
GET/api/v1/voice/sessions/:idvoice:readSingle voice session metadata.
GET/api/v1/voice/sessions/:id/transcriptvoice:readOrdered transcript lines (speaker: visitor | ai | agent).
POST/api/v1/voice/sessions/:id/endvoice:writeTerminate a live voice session and tear down the SFU room.
GET/api/v1/voice/settingsvoice:readRead org-level voice defaults (silence timeout, max call duration, provider, xAI model).
PUT/api/v1/voice/settingsvoice:writeUpdate org-level voice defaults. Takes effect on the next session.
POST/api/v1/voice/callback/requestvoice:writeFile a callback request against the voice queue. Body: { "session_id"?, "name"?, "phone"?, "email"?, "note"? } — at least one of phone or email is required.

Voice handoff (agent claim) remains seat-licensed and is initiated from the dashboard Live Conversations UI — it requires a User row with liveHandoffLicensed, not a service key, so it has no /api/v1 equivalent.

All update operations use POST on the resource path (POST /api/v1/leads/:id). PATCH on the same path stays accepted for one major version so existing integrations don’t break, but POST is the canonical method going forward.

Resource type discriminator

Every resource response carries a read-only object field naming the resource type ("lead", "conversation", "task", "message"). List responses carry object: "list" at the envelope and per-item object fields inside data[]. Use it to deserialize polymorphic payloads (e.g., webhooks, mixed-resource responses).

{
  "object": "lead",
  "id": "clx...",
  "stage": "qualified",
  "temperature": "warm"
}

Pagination

List endpoints return a data array plus a has_more boolean and a url field naming the endpoint. Use cursor pagination via starting_after=<resource_id> — the API returns the next page of items created before that cursor. limit is bounded at 200 (default 50).

GET /api/v1/leads?limit=20&starting_after=clx_abc123

{
  "object": "list",
  "data": [ { "object": "lead", "id": "clx_xyz789", ... }, ... ],
  "has_more": true,
  "url": "/api/v1/leads",

  // Legacy fields surfaced alongside the new envelope for one major
  // version. New consumers should read `data` + `has_more`.
  "leads":  [ /* same items as data[] */ ],
  "total":  142,
  "limit":  20,
  "offset": 0
}

Naming conventions

Query parameters, request body fields, and response field names use snake_case. Existing camelCase aliases on list filters (widgetId, assignedTo, leadId, updatedSince) and on PATCH bodies (jobTitle, assignedTo) continue to work for one major version — new code should use snake_case.

Rate limits

Developer keys are rate-limited per key: 100 reads/min and 30 writes/min. Every authenticated response carries the current state in headers so well-behaved clients can self-throttle:

Quincer-RateLimit-Limit: 100
Quincer-RateLimit-Remaining: 87
Quincer-RateLimit-Reset: 1714435260

Over the limit returns 429 Too Many Requests with both the standard Retry-After header (seconds until reset) and the Quincer-RateLimit-* headers. The error body indicates the remaining count.

HTTP/1.1 429 Too Many Requests
Retry-After: 42
Quincer-RateLimit-Limit: 100
Quincer-RateLimit-Remaining: 0
Quincer-RateLimit-Reset: 1714435260

{
  "error": "Rate limit exceeded. 0 read requests remaining. Retry after 42 seconds."
}

Webhooks

Subscribe to events under Dashboard → Settings → Webhooks. Quincer AI signs every payload with HMAC-SHA256 using the secret you set. Requests include Quincer-Signature (sha256=...), Quincer-Event, and Quincer-Timestamp. The legacy X-Quincer-* variants are also sent for one major version (RFC 6648 deprecated the X- prefix — existing subscribers reading X-Quincer-* keep working unchanged).

Events

EventFires when
conversation.message_receivedVisitor sends any message. Payload includes status (active or human_active) so subscribers know whether an external agent is driving.
conversation.takeoverA human or external agent claimed a conversation (dashboard takeover, Slack/Telegram approve, or POST /api/v1/conversations/:id/takeover). AI stops responding until handback fires. Payload: conversationId, widgetId, actorKind (internal_user or external_agent), actorUserId, actorName, optional externalLabel, optional surface (dashboard | slack | telegram | api).
conversation.handbackTakeover released — AI resumes. Same payload shape as conversation.takeover.
lead.createdA new lead record is captured.
lead.updatedAny lead field changes.
lead.stage_changedLead moves between pipeline stages. Payload includes both previous_stage (canonical) and previousStage (legacy alias).
task.createdAn agent task (follow-up, qualification, CRM sync, etc.) is scheduled.
task.completedA task finishes successfully.
email.sentA lead email goes out.
email.draft_createdAn AI-drafted email is ready for review. Email resources are managed via the dashboard — the payload is fully self-contained for downstream notification use.

Verifying signatures

const signature = req.headers["quincer-signature"];
// (or "x-quincer-signature" if you set up your webhook before April 2026)
const expected = "sha256=" + crypto
  .createHmac("sha256", process.env.QUINCER_WEBHOOK_SECRET)
  .update(req.rawBody)
  .digest("hex");
if (signature !== expected) {
  return res.status(401).send("invalid signature");
}

OpenClaw integration

Quincer AI ships a first-class OpenClaw skill so any OpenClaw agent can take over Quincer AI conversations, query leads, and log activity. Setup takes about a minute:

  1. In Quincer AI, open Dashboard → Integrations → OpenClaw and click Connect OpenClaw. This creates a dedicated "OpenClaw" team member in your workspace and issues an API token bound to it.
  2. Download SKILL.md and drop it in ~/.openclaw/workspace/skills/quincer/.
  3. Add the token to ~/.openclaw/openclaw.json under skills.entries.quincer.env.QUINCER_API_KEY.
  4. Restart the OpenClaw gateway. The quincer tool is now available.

OpenClaw actions (takeovers, replies) appear in your Quincer AI inbox under the OpenClaw team member, with their own avatar and audit trail — not as a generic "external agent."