Skip to content
On this page

API Reference

Complete reference for all Canny Bot API endpoints.

Base URL

https://api.canny.bot/api

Authentication

Include one of the following with every request:

API Key (Query Parameter):

?api_key=<your_api_key>

API Key (Header):

x-api-key: <your_api_key>

Widget Endpoints

Generate Widget ID

Create a new widget for your website.

POST /widget/generate
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "widgetId": "wgt_123_abc456",
    "embedCode": "<script src=\"...\"></script>"
  }
}

Get Widget Script

Retrieve the embeddable widget JavaScript.

GET /widget/script/:widget_id

Parameters:

  • widget_id (path): Widget ID

Response: JavaScript code (Content-Type: application/javascript)


Get Widget Info

Retrieve current widget configuration.

GET /widget/info
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "widgetId": "wgt_123_abc456",
    "embedCode": "<script src=\"...\"></script>",
    "color": "#667eea",
    "greeting": "Hello! How can I help you today?",
    "borderRadius": 16,
    "buttonRadius": 50,
    "buttonText": "Chat",
    "font": "Arial",
    "title": "Chat with us"
  }
}

Customize Widget

Update widget appearance and behavior.

PUT /widget/customize
x-api-key: <your_api_key>
Content-Type: application/json

{
  "widget_color": "#667eea",
  "widget_greeting": "Hello!",
  "widget_border_radius": 16,
  "widget_button_radius": 50,
  "widget_button_text": "Chat",
  "widget_font": "Arial",
  "widget_title": "Chat with us"
}

Response:

json
{
  "data": {
    "success": true
  }
}

Chat (Non-Streaming)

Send a message to the widget chat.

POST /widget/chat
x-api-key: <your_api_key>
Content-Type: application/json

{
  "widgetId": "wgt_123_abc456",
  "sessionId": "sess_abc123",
  "message": "What is your return policy?"
}

Response:

json
{
  "data": {
    "response": "Our return policy allows returns within 30 days...",
    "sessionId": "sess_abc123"
  }
}

Chat Stream

Send a message and receive a streaming response via Server-Sent Events.

POST /widget/chat/stream
Content-Type: application/json

{
  "widgetId": "wgt_123_abc456",
  "sessionId": "sess_abc123",
  "message": "What is your return policy?"
}

Required fields: widgetId, message

Optional fields: sessionId (if omitted, a new session is created)

Response: Content-Type: text/event-stream

data: {"type":"sources","sources":[{"title":"Return Policy","url":"https://..."}]}

data: {"type":"content","content":"Our return "}

data: {"type":"content","content":"policy allows "}

data: {"type":"content","content":"returns within 30 days..."}

data: {"type":"done"}

SSE event types:

TypeDescription
sourcesSource documents used for RAG (sent first, if any)
contentIncremental response token
doneStream complete
errorError occurred ({"type":"error","error":"message"})

Notes:

  • Guard rails are evaluated before generation; if triggered, a single content event with the override/correction is sent
  • Conversation history (up to 30 messages) is persisted per session
  • Monthly interaction limits apply — returns 429 when exceeded
  • Per-session rate limits apply — returns 429 with Retry-After header when exceeded

Semantic Search Endpoints

Find documents matching a query.

GET /semantic-search/search?query=<query>&limit=5&threshold=0.1
x-api-key: <your_api_key>

Query Parameters:

  • query (required): Search query
  • limit (optional): Max results (default: 5)
  • threshold (optional): Relevance threshold 0-1 (default: 0.1)
  • contentType (optional): Filter by content type

Response:

json
{
  "data": [
    {
      "id": "chunk_123",
      "title": "Return Policy",
      "textSnippet": "We accept returns within 30 days...",
      "fullContent": "Full content...",
      "score": 0.87,
      "documentId": "doc_1",
      "contentType": "page"
    }
  ],
  "meta": {
    "count": 1
  }
}

Answer

Get an AI-generated answer based on search results.

GET /semantic-search/answer?query=<query>&limit=5&threshold=0.1
x-api-key: <your_api_key>

Query Parameters:

  • query (required): Question
  • limit (optional): Source documents to use (default: 5)
  • threshold (optional): Relevance threshold (default: 0.1)
  • contentType (optional): Filter by content type

Response:

json
{
  "data": {
    "answer": "You can return items within 30 days...",
    "sources": [
      {
        "id": "chunk_123",
        "title": "Return Policy",
        "textSnippet": "We accept returns within 30 days...",
        "score": 0.87
      }
    ]
  },
  "meta": {
    "sourcesCount": 1
  }
}

Answer Stream

Get a streaming AI-generated answer.

GET /semantic-search/answer/stream?query=<query>&limit=5&threshold=0.1
x-api-key: <your_api_key>

Response Format (Server-Sent Events):

data: {"type":"sources","sources":[...],"uniqueDocs":[...]}
data: {"type":"content","content":"You can "}
data: {"type":"content","content":"return items..."}
data: {"type":"done"}

Lead Collection Endpoints

Get Lead Collection Config

Retrieve the current lead collection configuration for the authenticated user.

GET /lead-collection/config
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "id": 1,
    "documentId": "abc123",
    "enabled": true,
    "webhooks": [
      {
        "url": "https://hooks.example.com/lead",
        "active": true,
        "lead_statuses": ["success", "partial"],
        "headers": [{ "key": "Authorization", "value": "Bearer token123" }]
      }
    ],
    "inactivity_threshold_minutes": 10,
    "data_collection_parameters": {
      "name": "Extract visitor name",
      "email": "Extract email if provided"
    }
  }
}

Update Lead Collection Config

Update the lead collection configuration.

PUT /lead-collection/config
x-api-key: <your_api_key>
Content-Type: application/json

{
  "enabled": true,
  "webhooks": [
    {
      "url": "https://hooks.example.com/lead",
      "active": true,
      "lead_statuses": ["success", "partial", "failed"],
      "headers": [
        { "key": "Authorization", "value": "Bearer token123" }
      ]
    }
  ],
  "inactivity_threshold_minutes": 10,
  "data_collection_parameters": {
    "name": "Extract visitor name",
    "email": "Extract email if provided"
  }
}

Webhook Object:

FieldTypeDescription
urlstringWebhook endpoint URL
activebooleanWhether this webhook is enabled
lead_statusesstring[]Which lead statuses to send: "success", "partial", "failed". Empty = all
headersarrayCustom HTTP headers. Each header: { key, value, active }

Response: Same as GET config.


Retry Webhook

Re-send a lead to all active webhooks that match the lead's status.

POST /lead-collection/retry-webhook
x-api-key: <your_api_key>
Content-Type: application/json

{
  "lead_id": "abc123"
}

Response:

json
{
  "data": {
    "id": 1,
    "documentId": "abc123",
    "status": "success",
    "webhook_response": [
      {
        "url": "https://hooks.example.com/lead",
        "success": true,
        "status": 200,
        "data": { "received": true }
      }
    ],
    "sent_at": "2025-07-07T18:30:00.000Z"
  }
}

Get Leads (External API)

Retrieve collected leads via API key authentication. Supports filtering by status and pagination.

GET /lead-collection/leads?status=success&limit=50&page=1
x-api-key: <your_api_key>

Query Parameters:

  • status (optional): Filter by lead status — "success", "partial", or "failed"
  • limit (optional): Results per page (default: 50, max: 100)
  • page (optional): Page number (default: 1)

Response:

json
{
  "data": [
    {
      "documentId": "abc123",
      "extracted_data": {
        "name": "John Doe",
        "email": "john@example.com"
      },
      "chat_summary": "Visitor asked about pricing and provided contact details.",
      "status": "success",
      "createdAt": "2025-07-07T18:30:00.000Z",
      "sent_at": "2025-07-07T18:31:00.000Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 50,
      "total": 127,
      "pageCount": 3
    }
  }
}

Guard Rails Endpoints

List Guard Rails

GET /guard-rails
x-api-key: <your_api_key>

Response:

json
{
  "data": [
    {
      "id": 1,
      "documentId": "abc123",
      "name": "No PII",
      "description": "Block requests asking for personal information",
      "severity": "high",
      "correction_prompt": "Please don't share personal information.",
      "response_override": null,
      "email_contacts": ["admin@example.com"],
      "enabled": true,
      "trigger_count": 3
    }
  ]
}

Create Guard Rail

POST /guard-rails
x-api-key: <your_api_key>
Content-Type: application/json

{
  "name": "No PII",
  "description": "Block requests asking for personal information",
  "severity": "high",
  "correction_prompt": "Please don't share personal information.",
  "response_override": null,
  "email_contacts": ["admin@example.com"],
  "enabled": true
}

Fields:

FieldTypeRequiredDescription
namestringYesGuard rail name
descriptionstringYesWhat the guard rail detects
severitystringNo"low", "medium", "high", "critical" (default: "medium")
correction_promptstringNoPrompt sent to LLM when triggered
response_overridestringNoOverride the entire response when triggered
email_contactsstring[]NoEmail addresses to alert when triggered
enabledbooleanNoActive state (default: true)

Response: Same as a single guard rail object.


Update Guard Rail

PUT /guard-rails/:id
x-api-key: <your_api_key>
Content-Type: application/json

{
  "enabled": false,
  "severity": "critical"
}

All fields are optional — only provided fields are updated.

Response: Updated guard rail object.


Delete Guard Rail

DELETE /guard-rails/:id
x-api-key: <your_api_key>

Response:

json
{
  "success": true
}

Test Guard Rail

Test a guard rail against sample input using LLM classification.

POST /guard-rails/:id/test
x-api-key: <your_api_key>
Content-Type: application/json

{
  "input": "What is your social security number?"
}

Response:

json
{
  "data": {
    "triggered": true,
    "rail": {
      "id": "abc123",
      "name": "No PII",
      "severity": "high"
    },
    "correction": "Please don't share personal information.",
    "override": null
  }
}

Agent Script Endpoints

Get Agent Script

Retrieve the current agent script (system prompt) for the authenticated user.

GET /users/script
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "script": "You are a helpful assistant for ACME Corp..."
  }
}

Update Agent Script

PUT /users/script
x-api-key: <your_api_key>
Content-Type: application/json

{
  "script": "You are a helpful assistant for ACME Corp. Always greet users warmly..."
}

Response:

json
{
  "success": true,
  "script": "You are a helpful assistant for ACME Corp. Always greet users warmly..."
}

Rate Limit Config Endpoints

Get Rate Limit Config

GET /rate-limit/config
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "enabled": true,
    "max_messages_per_session": 20,
    "cooldown_seconds": 60,
    "max_messages_per_minute": 5,
    "custom_message": ""
  }
}

If no config exists, default values are returned.


Update Rate Limit Config

PUT /rate-limit/config
x-api-key: <your_api_key>
Content-Type: application/json

{
  "enabled": true,
  "max_messages_per_session": 30,
  "cooldown_seconds": 90,
  "max_messages_per_minute": 8,
  "custom_message": "Please wait a moment before sending another message."
}

All fields are optional — only provided fields are updated.

Response: Updated config object.


Analysis Config Endpoints

Get Analysis Config

GET /analysis/config
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "enabled": true,
    "chat_threshold": 50
  }
}

Update Analysis Config

PUT /analysis/config
x-api-key: <your_api_key>
Content-Type: application/json

{
  "enabled": true,
  "chat_threshold": 100
}

Response: Updated config object.


Trigger Analysis

Manually trigger longitudinal analysis for the authenticated user.

POST /analysis/trigger
x-api-key: <your_api_key>
Content-Type: application/json

{
  "language": "en"
}

Response:

json
{
  "data": {
    "message": "Analysis triggered successfully"
  }
}

Account Endpoints

Get Account Status

GET /account/status
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "account": "pro",
    "subscription": {
      "id": "sub_123",
      "status": "active",
      "currentPeriodEnd": 1720000000
    },
    "usage": {
      "current": 42,
      "limit": 1000,
      "remaining": 958,
      "percentUsed": 4
    },
    "limits": {
      "free": 100,
      "pro": 1000,
      "enterprise": 10000
    }
  }
}

Get Interaction History

GET /account/interactions?months=12
x-api-key: <your_api_key>

Query Parameters:

  • months (optional): Number of months of history to retrieve (default: 12)

Response:

json
{
  "data": {
    "history": [{ "month": 7, "year": 2025, "amount": 42, "label": "2025-07" }],
    "account": "pro",
    "limit": 1000
  }
}

User Settings Endpoints

Generate API Key

Generate a new API key for the authenticated user.

POST /users/generate-api-key
x-api-key: <your_api_key>

Response:

json
{
  "api_key": "sk_a1b2c3d4...",
  "message": "API key generated successfully"
}

Revoke API Key

POST /users/revoke-api-key
x-api-key: <your_api_key>

Response:

json
{
  "success": true,
  "message": "API key revoked successfully"
}

List Available Models

GET /users/models
x-api-key: <your_api_key>

Response:

json
{
  "data": [
    { "id": "openai/gpt-4", "name": "GPT-4" },
    { "id": "anthropic/claude-3-opus", "name": "Claude 3 Opus" }
  ]
}

Save LLM Model

Set the preferred LLM model and provider.

PUT /users/llm-model
x-api-key: <your_api_key>
Content-Type: application/json

{
  "llm_model": "openai/gpt-4",
  "llm_provider": "openai"
}

Response:

json
{
  "success": true,
  "llm_model": "openai/gpt-4",
  "llm_provider": "openai"
}

Document Endpoints

List Documents

Retrieve all documents for the authenticated user with pagination.

GET /docs?page=1&pageSize=100&sort=title:asc
x-api-key: <your_api_key>

Query Parameters:

  • page (optional): Page number (default: 1)
  • pageSize (optional): Items per page (default: 100)
  • sort (optional): Sort field, e.g. title:asc or updatedAt:desc

Response:

json
{
  "data": [
    {
      "id": 1,
      "documentId": "abc123",
      "title": "Return Policy",
      "content": "We accept returns within 30 days...",
      "url": "https://example.com/returns",
      "is_dynamic": false,
      "owner": 5,
      "createdAt": "2025-07-07T18:30:00.000Z",
      "updatedAt": "2025-07-07T18:30:00.000Z"
    }
  ],
  "meta": {
    "pagination": { "page": 1, "pageSize": 100, "total": 245 }
  }
}

Get Single Document

GET /docs/:id
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "id": 1,
    "documentId": "abc123",
    "title": "Return Policy",
    "content": "We accept returns within 30 days...",
    "url": "https://example.com/returns",
    "is_dynamic": false,
    "owner": 5,
    "createdAt": "2025-07-07T18:30:00.000Z",
    "updatedAt": "2025-07-07T18:30:00.000Z"
  }
}

Ingest Documents (Single or Batch)

The most powerful ingestion endpoint. Accepts raw content or URLs. When a URL is provided without content, the system scrapes it via Firecrawl v2 (with onlyCleanContent for automatic markdown cleaning) and generates embeddings.

Supports both single objects and batch arrays.

Single Document (Raw Content)

POST /docs/ingest
x-api-key: <your_api_key>
Content-Type: application/json

{
  "title": "Product: Widget Pro",
  "content": "The Widget Pro is our flagship product..."
}

Single Document (From URL — Auto-Scrape)

POST /docs/ingest
x-api-key: <your_api_key>
Content-Type: application/json

{
  "url": "https://example.com/products/widget-pro"
}

When only url is provided, the system scrapes the page using Firecrawl v2, extracts clean markdown (with onlyCleanContent and onlyMainContent), strips images, and uses the page title from metadata.

Batch Ingestion (Mixed Raw + URLs)

POST /docs/ingest
x-api-key: <your_api_key>
Content-Type: application/json

[
  {
    "title": "Product: Widget Pro",
    "content": "The Widget Pro is our flagship product..."
  },
  {
    "url": "https://example.com/products/widget-lite"
  },
  {
    "title": "Product: Gadget Max",
    "content": "The Gadget Max offers maximum performance...",
    "url": "https://example.com/products/gadget-max",
    "is_dynamic": true,
    "renew_interval_hours": 48
  }
]

Fields per item:

FieldTypeRequiredDescription
titlestringNo (auto-extracted if URL)Document title
contentstringYes (or provide url)Raw markdown content
urlstringNoSource URL; if no content, page is scraped
is_dynamicbooleanNoEnable auto-refresh from URL
renew_interval_hoursnumberNoHours between refreshes (if dynamic)

Response (batch):

json
{
  "success": true,
  "ingested": 3,
  "failed": 0,
  "results": [
    {
      "success": true,
      "documentId": "doc_abc123",
      "title": "Product: Widget Pro",
      "url": null
    },
    {
      "success": true,
      "documentId": "doc_def456",
      "title": "Widget Lite - Example Corp",
      "url": "https://example.com/products/widget-lite"
    },
    {
      "success": true,
      "documentId": "doc_ghi789",
      "title": "Product: Gadget Max",
      "url": "https://example.com/products/gadget-max"
    }
  ]
}

Notes:

  • All items are processed in parallel via Promise.all
  • Each item that includes a URL but no content triggers a Firecrawl v2 scrape
  • Embeddings are generated for every successfully created document
  • Image references are stripped from scraped markdown automatically

Update Document

Update a document. Embeddings are re-generated automatically.

PUT /docs/:id
x-api-key: <your_api_key>
Content-Type: application/json

{
  "data": {
    "title": "Updated Title",
    "content": "Updated content..."
  }
}

All fields in data are optional — only provided fields are updated.

Response: Updated document object.


Delete Document

Delete a document and its embeddings.

DELETE /docs/:id
x-api-key: <your_api_key>

Response:

json
{
  "data": {
    "documentId": "abc123",
    "title": "Return Policy"
  }
}

Batch Delete Documents

Delete multiple documents and their embeddings in a single request.

POST /docs/batch-delete
x-api-key: <your_api_key>
Content-Type: application/json

{
  "documentIds": ["doc_abc123", "doc_def456", "doc_ghi789"]
}

Response:

json
{
  "success": true,
  "deleted": 3,
  "failed": 0,
  "results": [
    { "id": "doc_abc123", "success": true },
    { "id": "doc_def456", "success": true },
    { "id": "doc_ghi789", "success": true }
  ]
}

Error Responses

400 Bad Request

json
{
  "error": "Bad Request",
  "message": "Query param is required"
}

401 Unauthorized

json
{
  "error": "Unauthorized",
  "message": "Authentication required. Use JWT token or api_key parameter"
}

404 Not Found

json
{
  "error": "Not Found",
  "message": "Widget not found"
}

429 Too Many Requests

json
{
  "error": "Rate limit exceeded",
  "message": "You have exceeded the rate limit of 10 requests per minute for your free plan.",
  "retryAfter": 45
}

500 Internal Server Error

json
{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred"
}

Rate Limits

Rate limits are enforced per plan:

PlanMonthly LimitRequests/Second
Free1001
Pro1,0005
EnterpriseUnlimited20

Rate Limiting

All authenticated API endpoints are subject to rate limiting based on your plan:

PlanRequests/Minute
Free10
Pro100
Enterprise1000

Rate limit information is included in response headers:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1703341234
Retry-After: 45

When the rate limit is exceeded, the API returns a 429 status code with retry information.


Response Codes

CodeMeaning
200Success
400Bad Request
401Unauthorized
404Not Found
429Too Many Requests (Rate Limited)
500Server Error