API & MCP
Available on Pro and Business. On other plans the API returns 402.
The API is for servers, scripts and coding agents. Never put an API key in browser code: /v1 and /mcp send no CORS headers, so browsers block cross-site calls.
API keys
Section titled “API keys”Create a key in Settings → API & MCP with Create API key. Only workspace owners and admins can manage keys. A key starts with fb_live_ and is shown once.
Send the key as a bearer token:
curl https://api.feedbotai.com/v1/bots \ -H "Authorization: Bearer $FEEDBOT_API_KEY"X-API-Key: <key> works too. A missing or revoked key gets 401 with WWW-Authenticate: Bearer.
Scopes
Section titled “Scopes”When you create a key, choose Read only or Read & write, or open Advanced and pick granular scopes.
| Scope | Allows |
|---|---|
read |
Every read: bots, conversations, stats, issues, the agent report, insights, leads, tickets and ticket types. |
write |
Everything: all reads and all writes. |
leads:read |
List, get and export leads. |
leads:write |
Mark leads as lead / not a lead. Includes leads:read. |
tickets:read |
List and get tickets and ticket types. |
tickets:write |
Create, update, comment on and resolve tickets. Includes tickets:read. |
issues:write |
Update product issues. Also allows reading issues and the agent report. |
insights:write |
Generate insight reports. Also allows reading insights. |
A call without the needed scope gets 403, for example {"error": "this API key lacks the \"tickets:write\" scope"}. Older keys were created as read or read + write and keep working.
Rate limits
Section titled “Rate limits”- 120 requests per minute per key. REST and MCP share this budget: every HTTP request to
/mcpcounts. - 30 write calls per minute per workspace:
POSTandPATCHrequests and MCP write tools.
Over the limit you get 429 with a Retry-After header. Responses also carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.
Conventions
Section titled “Conventions”Base URL: https://api.feedbotai.com. Responses are JSON wrapped in {"data": ...}.
OpenAPI
Section titled “OpenAPI”The OpenAPI 3.1 document describes every endpoint, parameter and response. It is public (no API key), so you can import it into Postman, Insomnia or Swagger UI, or generate a client from it:
curl https://api.feedbotai.com/v1/openapi.json -o feedbot-openapi.jsonErrors
Section titled “Errors”Errors are always JSON: {"error": "message"}, sometimes with extra fields (a duplicate ticket 409 also has ticketId).
| Status | When |
|---|---|
400 |
Invalid JSON, unknown JSON fields, an empty PATCH, or an invalid query parameter (date, limit, enum value, boolean, id, cursor). |
401 |
Missing or invalid API key. |
402 |
Your plan doesn’t include the API or this feature. |
403 |
The key lacks the needed scope. |
404 |
Unknown route, or the object doesn’t exist in your workspace. Objects of other workspaces are always 404. |
405 |
Method not allowed on this route. |
409 |
Conflict: an open ticket of that type already exists, the ticket is already resolved, or a request with the same Idempotency-Key is still running. |
422 |
The Idempotency-Key was already used for a different request. |
429 |
Rate limit exceeded. |
500 |
Server error. Safe to retry with the same Idempotency-Key. |
Dates and timestamps
Section titled “Dates and timestamps”All timestamps in responses are RFC 3339 in UTC, for example 2026-10-01T12:00:00Z.
Date parameters (from, to, since, updatedSince) accept RFC 3339 with any offset (converted to UTC) or YYYY-MM-DD (a UTC day). from, since and updatedSince are inclusive. to is exclusive for a timestamp, but to=2026-09-30 includes the whole of September 30.
Booleans are true or false. limit is a positive integer; values above the maximum (200) are lowered to it. The default is 50 (20 for insights).
Pagination
Section titled “Pagination”Lists return {"data": [...], "nextCursor": "..."}, including the short lists that are not paginated (/v1/bots, /v1/ticket-types), where nextCursor is always null. When nextCursor is not null, pass it as cursor to get the next page with the same filters. Cursors are opaque; don’t build them yourself.
curl "https://api.feedbotai.com/v1/conversations?botId=BOT_ID&limit=100&cursor=NEXT_CURSOR" \ -H "Authorization: Bearer $FEEDBOT_API_KEY"/v1/conversations still accepts the old before parameter and returns nextBefore when there is a next page. Both are deprecated; use cursor.
Idempotency-Key
Section titled “Idempotency-Key”Any POST accepts an Idempotency-Key header (1–255 characters, for example a UUID). POST /v1/tickets/{id}/resolve requires it.
- The same key with the same method, path and body within 24 hours returns the stored response again, with
Idempotent-Replayed: true. Nothing is done twice. - The same key with a different request gets
422. - While the first request is still running, a retry gets
409. 5xxresponses are not stored, so you can retry them with the same key.
Keys are per workspace and kept for 24 hours.
Endpoints
Section titled “Endpoints”| Method | Path | Scope | Description |
|---|---|---|---|
GET |
/v1/openapi.json |
none | The OpenAPI document. |
GET |
/v1/bots |
read |
Your bots. |
GET |
/v1/conversations |
read |
Search conversations. |
GET |
/v1/conversations/{id} |
read |
A conversation with its analysis and transcript. |
GET |
/v1/attachments/{id} |
read |
Redirects to a file a visitor attached (link valid for 10 minutes). |
GET |
/v1/stats |
read |
Counts for a period. |
GET |
/v1/issues |
read or issues:write |
Product issues, most reported first. |
GET |
/v1/issues/{id} |
read or issues:write |
One issue with quotes and linked conversations. |
PATCH |
/v1/issues/{id} |
issues:write |
Update an issue. |
GET |
/v1/reports/agent.md |
read or issues:write |
Agent-ready report in Markdown. |
GET |
/v1/insights |
read or insights:write |
Insight reports. |
GET |
/v1/insights/{id} |
read or insights:write |
One insight report. |
POST |
/v1/insights |
insights:write |
Generate an insight report. |
GET |
/v1/leads |
leads:read |
Leads. |
GET |
/v1/leads/{id} |
leads:read |
One lead. |
GET |
/v1/leads/export.csv |
leads:read |
Leads as CSV. |
POST |
/v1/leads/{id}/status |
leads:write |
Mark as lead or not a lead. |
GET |
/v1/ticket-types |
tickets:read |
Enabled ticket types. |
GET |
/v1/tickets |
tickets:read |
Tickets. |
GET |
/v1/tickets/{id} |
tickets:read |
One ticket with comments. |
POST |
/v1/tickets |
tickets:write |
Create a ticket. |
PATCH |
/v1/tickets/{id} |
tickets:write |
Update a ticket. |
POST |
/v1/tickets/{id}/comments |
tickets:write |
Add a team comment. |
POST |
/v1/tickets/{id}/resolve |
tickets:write |
Close a ticket, optionally replying to the customer. |
GET /v1/bots returns id, name, siteUrl, status, archivedAt, installedAt and createdAt for each bot. It is not paginated: nextCursor is always null.
Conversations
Section titled “Conversations”GET /v1/conversations, newest activity first. Filters:
| Parameter | Description |
|---|---|
botId |
One bot. |
q |
Full-text search in messages and summaries. |
category |
support, sales, payments, complaint, feedback, bug, partnership, other or useless. |
tag |
One tag. |
minPriority |
1–5. |
humanNeeded |
true: only conversations that need a person. |
leads |
true: only conversations of visitors who are leads. |
from, to |
Last message time. from defaults to 30 days ago. |
limit, cursor |
Pagination. |
Spam is never returned.
GET /v1/conversations/{id} returns {"conversation": {...}, "messages": [...], "truncated": false}. Messages have id, role (user, assistant or operator), content, createdAt and, for operator replies, author (the team member’s name). Visitor messages with files have attachments: id, name, contentType, size, expiresAt and url. The url is GET /v1/attachments/{id}: call it with your API key and it redirects (302) to the file, or answers 410 after the retention period. Up to the last 1,000 messages are returned; truncated: true means older ones were left out.
curl "https://api.feedbotai.com/v1/conversations?botId=BOT_ID&humanNeeded=true&from=2026-09-01" \ -H "Authorization: Bearer $FEEDBOT_API_KEY"GET /v1/stats?botId=&from=&to= (from defaults to 30 days ago) returns conversations, messages, leads, humanNeeded, resolved, avgSentiment, byCategory, topTags, openIssues and ctaClicks. Spam is excluded from every count. leads counts conversations whose visitor is a lead.
Product issues
Section titled “Product issues”GET /v1/issues filters: botId, status (open, in_progress, fixed, wontfix or all; default open and in_progress), type (bug, ux, feature_request, content_gap, pricing), minPriority (1-5), since (last report time), limit, cursor. Ordered by priority, then by number of reports, then by the latest report.
Each issue has a priority from 1 to 5 (5 = urgent). The AI sets it when it first finds the issue and raises it when a more urgent report comes in. It never lowers it, and once someone sets the priority (in the dashboard or with PATCH), the AI leaves it alone.
New reports are matched to existing issues of any status, so a closed issue doesn’t come back as a duplicate. A report from a user message written after the issue was closed reopens a fixed issue and sets regressedAt (a possible regression; the agent report marks it Reopened). On a wontfix issue the report is counted in reportsSinceClosed and the status stays. closedAt is when the issue was last marked fixed or wontfix.
PATCH /v1/issues/{id} accepts status, priority, fixedNote, externalRef and notifyReporters:
curl -X PATCH https://api.feedbotai.com/v1/issues/ISSUE_ID \ -H "Authorization: Bearer $FEEDBOT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"fixed","fixedNote":"CSV export now works in Safari","notifyReporters":true}'GET /v1/reports/agent.md takes the same filters as /v1/issues (without cursor) and returns Markdown.
Insight reports
Section titled “Insight reports”GET /v1/insights filters: botId, limit (default 20), cursor. Newest first.
POST /v1/insights with {"botId": "...", "from": "...", "to": "..."} (default: the last 30 days) generates a report. It needs on-demand reports, which are on Pro and Business. The response is 202 with {"id": "...", "status": "pending", "deduplicated": false}; poll GET /v1/insights/{id} until status is ready (about 30 seconds). If a report for the same bot and the same period (by UTC day) is already pending or processing, you get that one with deduplicated: true instead of a new one.
curl -X POST https://api.feedbotai.com/v1/insights \ -H "Authorization: Bearer $FEEDBOT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"botId":"BOT_ID","from":"2026-09-01","to":"2026-09-30"}'Leads follow the same rule as the Leads page. The lead id is the visitor id.
{ "id": "0b6f…", "name": "Ann Smith", "firstName": "Ann", "lastName": "Smith", "phone": "+1 555 010 2030", "country": "US", "company": "Acme Inc", "leadScore": 85, "leadStatus": "none", "summary": "Team of 5, wants Pro yearly, asked about SSO.", "bot": { "id": "5521cdf1-…", "name": "Acme" }, "userId": "", "verified": false, "conversationsCount": 2, "conversationId": "9f0c…", "lastConversationId": "9f0c…", "conversationUrl": "https://app.feedbotai.com/c/9f0c…", "pageUrl": "https://acme.io/pricing", "firstSeenAt": "2026-09-29T10:02:11Z", "lastSeenAt": "2026-09-29T10:15:40Z", "exportedAt": null}leadStatus:lead(marked by your team),none(listed because of the AI score) ornot_lead(dismissed).leadScoreis the highest score of the visitor and their last 5 conversations.summaryis the latest summary of those conversations.exportedAtis when the lead was sent to your CRM webhook, ornull.conversationIdequalslastConversationId; it matches thelead.createdwebhook.
GET /v1/leads filters: botId, q (name, email, phone or user id), includeAnonymous=true (also visitors with no contact), marked (true: only marked by your team; false: only listed by the AI score), since (last seen), limit, cursor. Most recently seen first.
GET /v1/leads/{id} returns any visitor of your workspace; check leadStatus.
GET /v1/leads/export.csv takes the same filters and returns the CSV export (up to 10,000 rows).
POST /v1/leads/{id}/status with {"status": "lead"} or {"status": "not_lead"} marks the visitor. It is idempotent and answers at once: it never waits for your CRM. Marking as lead queues the lead for your CRM webhook, like the dashboard does; it is sent in the background within seconds, and a failed delivery is retried automatically. The response is {"data": <lead>, "export": "..."}:
export |
Meaning |
|---|---|
queued |
The lead will be sent to your CRM webhook. Watch exportedAt on GET /v1/leads/{id} to see when it was delivered. |
skipped |
Nothing to send: the status is not_lead, or the lead was already sent. |
not_configured |
The workspace has no enabled CRM webhook, or the plan doesn’t include webhooks. |
The response also has exported (whether the lead had been sent before this call). It is deprecated: use export and data.exportedAt.
curl -X POST https://api.feedbotai.com/v1/leads/VISITOR_ID/status \ -H "Authorization: Bearer $FEEDBOT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"lead"}'Tickets
Section titled “Tickets”Tickets are requests only your team can handle.
{ "id": "7d1e…", "botId": "5521cdf1-…", "botName": "Acme", "conversationId": "9f0c…", "conversationUrl": "https://app.feedbotai.com/c/9f0c…", "url": "https://app.feedbotai.com/t/7d1e…", "type": "refund", "typeName": "Refund", "status": "open", "priority": 4, "title": "Refund · order #1234", "description": "Charged twice for the yearly plan.", "source": "bot", "dueAt": null, "resolution": "", "visitor": { "id": "0b6f…", "name": "Ann Smith", "email": "[email protected]", "phone": "", "country": "US" }, "canReply": true, "createdAt": "2026-09-29T10:16:05Z", "updatedAt": "2026-09-29T10:16:05Z", "resolvedAt": null}source is ai, bot, manual or api. priority is 1 to 5 (5 = urgent): the AI sets it for the tickets it creates, and other tickets start at 3. canReply is false for conversations from legacy widgets, which can’t receive replies.
GET /v1/ticket-types lists the enabled types: key, name, description, emoji.
GET /v1/tickets filters: botId, status (open, in_progress, done or all; default open and in_progress), type (a type key), minPriority (1-5), assignee (a member’s email, or none for unassigned), conversationId, q (case-insensitive text in the title, description or resolution), since (creation time), updatedSince (last change: status, fields or a comment), limit, cursor. Newest first.
To sync tickets into another tool, poll with status=all&updatedSince=<time of your last sync>.
GET /v1/tickets/{id} adds comments: id, body, createdAt and author ({"type": "user", "name", "email"} for team members, {"type": "api", "name"} for comments posted with an API key).
POST /v1/tickets with conversationId, type (an enabled type key) and optional title, description and priority (1-5, default 3) creates a ticket with source api and notifies the team. Without a title, one is made from the description or the type. If the conversation already has an open ticket of that type, you get it back with 200 and "created": false instead of 201 and "created": true.
PATCH /v1/tickets/{id} accepts status, type, priority, title, description, dueAt (a date, or null) and assigneeEmail (a workspace member’s email, or null to unassign). Moving a ticket to a type, or reopening it, when the conversation already has an open ticket of that type gets 409 with the other ticket’s ticketId. Setting status to done resolves the ticket without a reply.
POST /v1/tickets/{id}/comments with {"body": "..."} (up to 5,000 characters) adds an internal comment. Customers never see it. In the dashboard it shows the API key’s name.
curl -X POST https://api.feedbotai.com/v1/tickets/TICKET_ID/comments \ -H "Authorization: Bearer $FEEDBOT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"body":"Refund issued in Stripe: re_3Q…"}'POST /v1/tickets/{id}/resolve marks the ticket done. Body, all optional:
| Field | Description |
|---|---|
reply |
Sent to the customer’s chat as a team reply, signed with the API key’s name. Empty = just close the ticket. |
sendEmail |
true: also email the reply now. Default false. |
closeConversation |
true: also close the conversation in the inbox. Default false. |
It requires an Idempotency-Key header, so a retry never sends the reply twice. A reply to a legacy-widget conversation gets 400, a reply on a ticket that is already done gets 409. The response is {"ticket", "replied", "emailQueued", "emailError", "conversationClosed"}.
curl -X POST https://api.feedbotai.com/v1/tickets/TICKET_ID/resolve \ -H "Authorization: Bearer $FEEDBOT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"reply":"Hi Ann, we refunded the second charge. It will show up in 5–10 days.","sendEmail":true}'The reply goes to a real customer. Check it before you automate this.
MCP server
Section titled “MCP server”The MCP server is at https://api.feedbotai.com/mcp. It uses Streamable HTTP, the same API keys and scopes, and the same rate limits.
Claude Code
Section titled “Claude Code”claude mcp add --transport http feedbot https://api.feedbotai.com/mcp \ --header "Authorization: Bearer $FEEDBOT_API_KEY"[mcp_servers.feedbot]url = "https://api.feedbotai.com/mcp"bearer_token_env_var = "FEEDBOT_API_KEY"Other MCP clients work the same way: HTTP transport, the URL above and the Authorization header. Settings → API & MCP shows these snippets.
Read-only tools are marked with readOnlyHint, so clients can run them without asking. resolve_ticket is marked destructive.
| Tool | Scope | What it does |
|---|---|---|
list_bots |
read |
List your bots ({"data", "nextCursor": null}). |
get_agent_report |
read |
Markdown report of issues with user quotes and acceptance criteria, highest priority first, then most reported. Args: botId, status, type, minPriority, since, limit. |
list_product_issues |
read |
The same issues as JSON. Args: botId, status, type, minPriority, since, limit, cursor. |
get_issue |
read |
One issue with all evidence. |
mark_issue_fixed |
issues:write |
Set an issue’s status (default fixed) with a note and an optional externalRef. notifyReporters: true emails the users who reported it. |
set_issue_priority |
issues:write |
Set an issue’s priority (1-5). The AI no longer changes it afterwards. |
search_conversations |
read |
Search by text (query, or q like the REST API), botId, category, tag, priority, humanNeeded or leads over the last days (default 30). Args also: limit, cursor. |
get_conversation |
read |
Transcript (last 200 messages, including team replies with their author) and analysis. Says when older messages were left out. |
get_insight_report |
read |
The latest ready insight report for a bot, or one by id. |
list_insight_reports |
read |
Insight reports with their status. Args: botId, limit, cursor. |
generate_insight_report |
insights:write |
Start a new report for the last days (Pro and Business). Returns the pending report if one for the same period is already running. |
get_stats |
read |
Conversation, lead and issue counts for the last days. |
list_leads |
leads:read |
Leads. Args: botId, q, includeAnonymous, marked, since, limit, cursor. |
get_lead |
leads:read |
One lead by visitor id. |
mark_lead |
leads:write |
Mark a visitor as lead or not_lead. Returns {"data", "export"} at once, like POST /v1/leads/{id}/status. |
list_ticket_types |
tickets:read |
Enabled ticket types ({"data", "nextCursor": null}). |
list_tickets |
tickets:read |
Tickets. Args: botId, status, type, minPriority, assignee, conversationId, q, since, updatedSince, limit, cursor. |
get_ticket |
tickets:read |
One ticket with comments and the conversation link. |
update_ticket |
tickets:write |
Change status, type, priority, dueAt or assigneeEmail. |
comment_ticket |
tickets:write |
Add an internal team comment. |
resolve_ticket |
tickets:write |
Close a ticket, optionally replying to the customer (reply, sendEmail, closeConversation). Destructive: the agent must show you the exact reply and get your confirmation before sending it. |
since and updatedSince accept RFC 3339 or YYYY-MM-DD. Lists return {"data": [...], "nextCursor": ...} like the REST API. Tool errors (missing scope, not found, invalid arguments) are returned as tool results with isError: true.
Example prompts
Section titled “Example prompts”Show me the top 5 open Feedbot issues and which pages they affect.
Fix the most reported Feedbot issue, run the tests, then mark it fixed with a short note.
List open Feedbot refund tickets and draft a reply for each. Don’t send anything until I approve.
Webhooks
Section titled “Webhooks”Feedbot can also push events to your server: conversation.analyzed, digests, ticket.created and ticket.resolved through a webhook notification channel, and lead.created through the CRM lead webhook. Webhooks are on Pro and Business.
Closing the loop
Section titled “Closing the loop”When an issue is marked fixed (by the API, MCP, the CLI or in the dashboard):
- for 30 days the bot knows about the fix: if a visitor mentions the problem, it says it has been fixed and asks them to try again;
- if you set
notifyReporters, users who reported the issue and left an email get a “the issue you reported is fixed” email with your note.
Changes in October 2026
Section titled “Changes in October 2026”If you used the API before October 2026:
GET /v1/statsno longer returnscostUsd.byCategoryandtopTagsexclude spam, like the totals.leads=trueon/v1/conversationsandleadsin stats now use the lead rule of the Leads page (marked by your team, or score 50+ from a visitor you can reach and didn’t dismiss) instead of the conversation’s score alone.- Transcripts include team replies (
role: "operator"withauthor). limitabove 200 is lowered to 200 instead of falling back to 50. Pagination usescursor/nextCursor;before/nextBeforestill work but are deprecated.- Invalid parameters (dates,
limit, enum values, unknown JSON fields, an emptyPATCH) return400instead of being ignored. - Read-only keys are checked on every read; see scopes.
/v1and/mcpno longer send CORS headers.- Webhooks use a versioned envelope (
version: 2); the old fields are still sent. POST /v1/leads/{id}/statusandmark_leadno longer wait for the CRM webhook: they returnexport(queued,skippedornot_configured) and the lead is sent in the background.exportedis deprecated./v1/botsand/v1/ticket-types(and thelist_bots/list_ticket_typestools) returnnextCursor: nullnext todata.- New:
qandupdatedSinceon/v1/ticketsandlist_tickets(which also takesconversationId), and the public OpenAPI document. - Ticket events go only to rules with Tickets: new and resolved on. It is on for every existing rule, so nothing changes until you turn it off.