Appearance
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:
| Type | Description |
|---|---|
sources | Source documents used for RAG (sent first, if any) |
content | Incremental response token |
done | Stream complete |
error | Error occurred ({"type":"error","error":"message"}) |
Notes:
- Guard rails are evaluated before generation; if triggered, a single
contentevent with the override/correction is sent - Conversation history (up to 30 messages) is persisted per session
- Monthly interaction limits apply — returns
429when exceeded - Per-session rate limits apply — returns
429withRetry-Afterheader when exceeded
Semantic Search Endpoints
Search
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 querylimit(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): Questionlimit(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:
| Field | Type | Description |
|---|---|---|
url | string | Webhook endpoint URL |
active | boolean | Whether this webhook is enabled |
lead_statuses | string[] | Which lead statuses to send: "success", "partial", "failed". Empty = all |
headers | array | Custom 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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Guard rail name |
description | string | Yes | What the guard rail detects |
severity | string | No | "low", "medium", "high", "critical" (default: "medium") |
correction_prompt | string | No | Prompt sent to LLM when triggered |
response_override | string | No | Override the entire response when triggered |
email_contacts | string[] | No | Email addresses to alert when triggered |
enabled | boolean | No | Active 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:ascorupdatedAt: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:
| Field | Type | Required | Description |
|---|---|---|---|
title | string | No (auto-extracted if URL) | Document title |
content | string | Yes (or provide url) | Raw markdown content |
url | string | No | Source URL; if no content, page is scraped |
is_dynamic | boolean | No | Enable auto-refresh from URL |
renew_interval_hours | number | No | Hours 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:
| Plan | Monthly Limit | Requests/Second |
|---|---|---|
| Free | 100 | 1 |
| Pro | 1,000 | 5 |
| Enterprise | Unlimited | 20 |
Rate Limiting
All authenticated API endpoints are subject to rate limiting based on your plan:
| Plan | Requests/Minute |
|---|---|
| Free | 10 |
| Pro | 100 |
| Enterprise | 1000 |
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
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 429 | Too Many Requests (Rate Limited) |
| 500 | Server Error |