API
The Yamidoo API gives your own code, and AI assistants you run, the same view of a project as the dashboard: its conversations, analytics and content gaps, plus write access to the hand-written parts of its knowledge base. A typical use is a weekly routine that reads last week’s chats and the questions the AI couldn’t answer, then adds Q&A or a doc article to close the gaps.
Available on the Plus and Pro plans.
Get an API key
Section titled “Get an API key”In the dashboard open your project → Integrations → API and click Create key. The key is shown once; store it somewhere safe. A site can have up to 5 active keys, and you can revoke any of them at any time.
Keys belong to one project. Every endpoint reads and writes that project only; there is no cross-project access.
Basics
Section titled “Basics”https://app.yamidoo.ai/api/v1/…Authorization: Bearer yk_live_…Content-Type: application/json- Times are ISO 8601 in UTC. Query parameters that take a time (
since,until,before) accept any ISO date or date-time. - Lists are newest first. When a list has more, the response includes
next_cursor; pass it asbeforeto get the next page. - Errors come back as
{ "error": "…" }with a matching status code, anddetailswhen a field failed validation. - Visitor names and emails are included where the dashboard shows them. Keep keys on your server; never put one in a browser.
| Endpoint | What it does |
|---|---|
GET /conversations |
List conversations, optionally with transcripts. |
GET /conversations/:id |
One conversation with its transcript. |
GET /analytics |
The Analytics page’s numbers for a period. |
GET /content-gaps |
Questions the AI couldn’t answer well. |
GET /content-gaps/topics |
Those questions grouped into topics. |
PATCH /content-gaps/:id |
Mark a gap solved or ignored. |
GET /sources |
Every source the AI answers from. |
GET /sources/:id |
One source; text sources include their content. |
POST /sources |
Add a text source. |
PATCH /sources/:id |
Edit a text source. |
POST /sources/:id/sync |
Re-sync a sitemap, crawl, URL-list or file source. |
GET /qa |
The Q&A pairs. |
POST /qa |
Add Q&A pairs. |
PATCH /qa/:id, DELETE /qa/:id |
Edit or remove a pair. |
POST /draft |
Draft a reply to a customer’s email. |
Conversations
Section titled “Conversations”GET /api/v1/conversations
Section titled “GET /api/v1/conversations”| Parameter | Description |
|---|---|
since, until |
Only conversations started in this window. |
status |
ai, waiting_human, with_agent or closed. |
placement |
floating, inline or iframe: where the chat was installed. |
placement_label |
Your data-placement label, e.g. contact. |
tag |
A tag name, as shown in the inbox. |
rated=true |
Only conversations the visitor rated. |
include=messages |
Embed each conversation’s transcript. Limit is then 50 per page. |
limit |
1 to 200, default 50. |
before |
The next_cursor from the previous page. |
curl "https://app.yamidoo.ai/api/v1/conversations?since=2026-10-01&include=messages" \ -H "Authorization: Bearer yk_live_…"{ "conversations": [ { "id": "77b4d6ae-…", "status": "closed", "created_at": "2026-10-06T13:34:11Z", "last_message_at": "2026-10-06T13:41:02Z", "ended_by": "visitor", "ended_at": "2026-10-06T13:41:02Z", "rating": 4, "human_involved": false, "placement": "inline", "placement_label": "contact", "start_page": "https://example.com/contact/", "visitor": { "name": "Jane", "email": "jane@example.com", "email_verified": false, "country": "DE", "country_name": "Germany" }, "tags": ["refund"], "message_count": 6, "messages": [ { "id": "…", "role": "visitor", "content": "Can I get a refund?", "created_at": "…", "rating": null, "rating_reason": null, "confidence": null, "answer_status": null, "sources": [], "has_attachments": false }, { "id": "…", "role": "ai", "content": "Yes, within 14 days of purchase. …", "created_at": "…", "rating": 1, "rating_reason": null, "confidence": 0.81, "answer_status": "answered", "sources": [{ "title": "Refund policy", "url": "https://example.com/refunds/" }], "has_attachments": false } ] } ], "next_cursor": "2026-10-05T09:12:00Z"}Message roles are visitor, ai, agent (a teammate) and system (notes
such as “Our team is away right now”). On AI messages, rating is the
visitor’s thumbs (1 or -1), rating_reason the reason after a thumbs-down
(wrong, incomplete, outdated), confidence the retrieval match from 0
to 1, and answer_status the classifier’s verdict: answered, partial,
unanswered or not_question.
human_involved is true when a teammate replied in the chat; ended_by is
agent, visitor or auto (closed for inactivity).
GET /api/v1/conversations/:id
Section titled “GET /api/v1/conversations/:id”One conversation in the same shape, always with messages.
Analytics
Section titled “Analytics”GET /api/v1/analytics
Section titled “GET /api/v1/analytics”| Parameter | Description |
|---|---|
days |
1 to 365, default 30. |
since, until |
An explicit window instead of days. |
{ "period": { "since": "2026-09-07T00:00:00Z", "until": "2026-10-07T00:00:00Z" }, "conversations": 37, "messages": 122, "visitor_messages": 52, "ai_messages": 36, "handoffs": 9, "handoff_rate": 24.3, "avg_ai_response_seconds": 4.9, "csat": { "average": 4.3, "ai_only": 4.1, "with_team": 4.8, "ratings": 12 }, "helpful_answers": { "rate": 83.3, "rated": 18 }, "by_status": { "closed": 35, "waiting_human": 2 }, "by_ended_by": { "visitor": 19, "agent": 7, "auto": 6 }, "by_placement": { "floating": { "conversations": 30, "labels": {} }, "inline": { "conversations": 7, "labels": { "contact": 7 } } }}Rates are percentages. csat is the average of the 1 to 5 conversation
ratings, split into chats the AI handled alone and chats a teammate replied
in. helpful_answers.rate is the share of rated AI answers that got a thumbs
up. The figures cover up to 10,000 conversations in the period; truncated: true tells you when a period had more.
Content gaps
Section titled “Content gaps”GET /api/v1/content-gaps
Section titled “GET /api/v1/content-gaps”Each item is an AI answer that was classified unanswered or partial, or
that the visitor rated thumbs-down, together with the question that preceded
it. These are the same items as the dashboard’s Content gaps page.
| Parameter | Description |
|---|---|
status |
open (default) or resolved. |
kind |
unanswered, partial or downvoted. |
since |
Only answers given after this time. |
limit, before |
Paging, as for conversations. |
{ "gaps": [ { "id": "889ee91e-…", "conversation_id": "a1698b35-…", "kind": "unanswered", "question": "Can I use the theme on a staging site without a second license?", "answer": "I don't have information about staging sites in the content I have. …", "answer_status": "unanswered", "rating": null, "rating_reason": null, "confidence": 0.31, "sources": [], "resolution": null, "resolved_at": null, "created_at": "2026-10-05T08:40:50Z" } ], "next_cursor": null}GET /api/v1/content-gaps/topics
Section titled “GET /api/v1/content-gaps/topics”The open gaps grouped into topics by the periodic rebuild, with the
dashboard’s suggested question and outline for each, counts by kind, and
last_7_days versus previous_7_days for the trend. computed_at says when
the grouping last ran.
PATCH /api/v1/content-gaps/:id
Section titled “PATCH /api/v1/content-gaps/:id”{ "resolution": "solved" }solved or ignored removes the gap from the open list and from topics;
null restores it. When your script adds a Q&A pair that answers a gap, mark
the gap solved in the same run.
Sources
Section titled “Sources”GET /api/v1/sources
Section titled “GET /api/v1/sources”Every source, newest first: id, type (sitemap, website_crawl,
url_list, file, text or qa), title, status (pending,
processing, ready or error), pages indexed, error, last_synced_at
and refresh. Sitemap, crawl and URL-list sources also include their url or
urls and patterns; Q&A sources the number of pairs.
GET /api/v1/sources/:id
Section titled “GET /api/v1/sources/:id”One source. Text sources include content.
POST /api/v1/sources
Section titled “POST /api/v1/sources”Add a text source: plain text or Markdown, indexed right away. Use it for doc articles, policies or notes the AI should answer from.
{ "type": "text", "title": "Staging sites", "content": "# Staging sites\n\nYou can use your license on a staging site …" }title is optional; the first line is used when it’s missing. content is up
to 200,000 characters. Returns the source and the number of chunks indexed.
Other source types are added from the dashboard, where you can scan a sitemap
and choose sections.
PATCH /api/v1/sources/:id
Section titled “PATCH /api/v1/sources/:id”Change a text source’s title or content. The source is re-indexed. Only
text sources can be edited through the API.
POST /api/v1/sources/:id/sync
Section titled “POST /api/v1/sources/:id/sync”Re-sync a sitemap, crawl, URL-list or file source in the background, the same
as the dashboard’s re-sync button. Returns 202 with queued: true, or
queued: false when a sync is already running. Text and Q&A sources are
indexed when saved and return 400.
Hand-written questions and answers, which the AI treats as authoritative over crawled pages. A project can have up to 500 pairs.
GET /api/v1/qa
Section titled “GET /api/v1/qa”{ "pairs": [ { "id": "583f6d74-…", "question": "How many sites can I use a theme on?", "answer": "As many as you like. …" } ] }POST /api/v1/qa
Section titled “POST /api/v1/qa”{ "pairs": [ { "question": "…", "answer": "…" } ] }Adds the pairs (1 to 200 per call) and keeps the existing ones. Returns the
added pairs with their ids and the new total. The whole list is re-indexed
on every write, so batch additions into one call.
PATCH /api/v1/qa/:id and DELETE /api/v1/qa/:id
Section titled “PATCH /api/v1/qa/:id and DELETE /api/v1/qa/:id”Change a pair’s question or answer, or remove it.
Draft reply
Section titled “Draft reply”POST /api/v1/draft
Section titled “POST /api/v1/draft”Send a customer’s message, get back a reply drafted the way the widget would answer it. Nothing is sent to the customer: you decide what to do with the draft, whether that’s pasting it into Help Scout, showing it to an agent, or sending it from your own code. Each request counts as one conversation, the same as a chat.
Request
Section titled “Request”POST https://app.yamidoo.ai/api/v1/draftAuthorization: Bearer yk_live_…Content-Type: application/json| Field | Type | Description |
|---|---|---|
message |
string | Required. The customer’s latest message, the one to reply to. Up to 12,000 characters. |
subject |
string | The email subject, if there is one. Gives the draft context. |
history |
array | Earlier turns of the thread, oldest first, as { "role": "customer" | "agent", "content": "…" }. Up to 20. |
customer.name |
string | The customer’s name. Used sparingly in the draft. |
customer.email |
string | The customer’s email. With a store connected under Integrations → Customer data, and the AI switch on, the draft can use their orders, licenses and subscriptions. |
curl https://app.yamidoo.ai/api/v1/draft \ -H "Authorization: Bearer yk_live_…" \ -H "Content-Type: application/json" \ -d '{ "subject": "License question", "message": "Hi, when does my license expire and can I still get updates?", "customer": { "name": "Jane", "email": "jane@example.com" } }'Response
Section titled “Response”{ "needs_reply": true, "skip_reason": null, "draft": "Hi Jane,\n\nYour Inspiro license runs until 14 March 2027, so you'll keep getting updates until then. …\n\nBest regards,\nThe WPZOOM team", "sources": [ { "title": "License renewals – WPZOOM", "url": "https://www.wpzoom.com/docs/renewals/" } ], "grounded": true, "confidence": 0.82, "needs_human": false, "customer_data_used": true, "model": "claude-sonnet-5"}| Field | Description |
|---|---|
needs_reply |
false when the message should not get a reply: a bounce notice, an auto-reply, a newsletter, a system notification or spam. draft is empty then and skip_reason says why. |
skip_reason |
A few words on why no reply was drafted, or null. |
draft |
Empty when needs_reply is false. Otherwise the reply, written as a complete email: a greeting (Hi Jane, or Hi there,), the answer, and Best regards, signed with your site name. First person as your team, light Markdown (bold, lists, links). |
sources |
The pages the draft was answered from. Show them to the agent, or leave them out of the reply. |
grounded |
true when relevant content was found. false means the draft could not be answered from your site. |
confidence |
How well your content matched the question, from 0 to 1. |
needs_human |
true when the draft isn’t grounded or confidence is low. Treat these as “read before sending”. |
customer_data_used |
true when the customer’s account data was included. |
model |
The model that wrote the draft. Depends on your plan. |
The draft follows the same rules as the widget: it answers from your content, quotes prices and figures exactly, and says when it doesn’t have the answer rather than guessing. Custom instructions under Settings → Behavior apply here too.
Errors
Section titled “Errors”| Status | Meaning |
|---|---|
400 |
A parameter or body field is invalid. The response names it. |
401 |
Missing, invalid or revoked API key. |
402 |
The plan’s limit is reached: monthly conversations (drafts) or indexed pages (sources). |
403 |
The site’s plan doesn’t include the API. |
404 |
No such conversation, source, pair or gap in this project. |
429 |
Rate limit hit for this key. Retry after a minute. |
503 |
Drafting is temporarily unavailable. Retry. |
Limits
Section titled “Limits”Per key, per minute: 120 reads, 30 source writes, 10 Q&A writes and 60 drafts. Reading data is free; each draft counts as one conversation against the plan, and sources and Q&A count toward the plan’s indexed pages like anything added in the dashboard.
Example: a Help Scout draft
Section titled “Example: a Help Scout draft”Using Help Scout? The built-in Help Scout integration does this for you, with no code. The script below is for when you want to run it yourself.
A small script that runs when a new conversation arrives, drafts a reply and saves it as a note so the agent sees it before replying:
const res = await fetch('https://app.yamidoo.ai/api/v1/draft', { method: 'POST', headers: { Authorization: `Bearer ${process.env.YAMIDOO_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ subject: conversation.subject, message: latestCustomerMessage.text, history: earlierThreads.map((t) => ({ role: t.createdBy.type === 'customer' ? 'customer' : 'agent', content: t.text, })), customer: { name: conversation.customer.firstName, email: conversation.customer.email }, }),});const { draft, needs_human, sources } = await res.json();
await helpscout.addNote(conversation.id, [ needs_human ? '⚠️ Low confidence — check before sending.' : '', draft, sources.length ? '\nSources: ' + sources.map((s) => s.url).join(', ') : '',].join('\n'));