Using Claude or another AI agent? Give it this link: https://tonetuned.rjscode.com/llms.txt. Create an API key on the Developers page.
Tonetuned API
Tonetuned builds FAQ assistants ("agents") that answer a business's customers in the owner's voice and never say anything the owner did not approve. Each agent has knowledge items (FAQs, facts, documents), a persona (tone sliders and hard rules) and a public chat link and website widget. This API lets you do everything the owner's dashboard does: manage agents, knowledge and persona, import files and web pages with a reviewed disclosure policy, test answers, and read conversations, leads and unanswered questions.
Human-readable copy of this file: https://tonetuned.rjscode.com/docs/api
Authentication
Every request needs the owner's API key in a header:
Authorization: Bearer tt_live_YOUR_KEYThe owner creates keys at https://tonetuned.rjscode.com/app/developers (the full key is shown once). Keys start with tt_live_. A key has full access to the owner's account, so never print it, log it or put it in a URL. If a key leaks, the owner revokes it on the same page.
In the examples below, set the key once:
export TONETUNED_KEY=tt_live_YOUR_KEYBase URL
https://tonetuned.rjscode.com/api/v1JSON in, JSON out. Send Content-Type: application/json with a body. Ids are UUIDs.
Quick start for agents
GET /agentsto find the agent (orPOST /agentsto create one).- Add knowledge: for the owner's files or website, run an import (see "Imports and the disclosure policy"); for a few FAQs you write yourself,
POST /agents/{id}/knowledge. PATCH /agents/{id}to tune the voice.POST /agents/{id}/previewwith a few real customer questions, and fix gaps until the answers are right.- Give the owner the agent's
chat_urlorembed_snippet. Platform-by-platform install steps (WordPress, Shopify, Wix, Next.js and more): https://tonetuned.rjscode.com/docs/install?agent=PUBLIC_ID
Visibility levels (read this before adding knowledge)
Every knowledge item has a visibility. It is enforced by the database, not by asking the model:
- public: any visitor can get this answer.
- customer: only used when the agent's disclosure slider is 50 or higher and a visitor asks about that topic directly (for example prices of extras). Never volunteered.
- internal: never sent to the model for visitors. Only the owner sees it, in preview with
staffView: true. Use it for notes like door codes, private phone numbers, supplier margins.
When unsure, choose internal. Ask the owner before making anything public that looks private.
Account
GET /me
Plan, usage this month, quota, agent limit, business name and unpaid balance (Free-plan grace period, in euro cents).
curl https://tonetuned.rjscode.com/api/v1/me -H "Authorization: Bearer $TONETUNED_KEY"{
"plan": "free",
"business_name": "Lakeside Apartments",
"usage": { "month": "2026-10-01", "messages": 12, "quota": 100 },
"agents": { "used": 1, "limit": 1 },
"balance_cents": 0,
"grace_started_at": null
}PATCH /me
Set the business name the agents introduce themselves with ("the AI assistant for ...").
curl -X PATCH https://tonetuned.rjscode.com/api/v1/me -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" -d '{"business_name":"Lakeside Apartments"}'Returns the same shape as GET /me.
Agents
GET /agents
curl https://tonetuned.rjscode.com/api/v1/agents -H "Authorization: Bearer $TONETUNED_KEY"{
"agents": [
{
"id": "6f1c2a9e-3b7d-4c55-9a51-0d1e2f3a4b5c",
"name": "Ada",
"public_id": "k3x9q2m7ab",
"is_example": false,
"created_at": "2026-10-27T09:12:00Z",
"updated_at": "2026-10-27T09:12:00Z",
"chat_url": "https://tonetuned.rjscode.com/a/k3x9q2m7ab",
"embed_snippet": "<script src=\"https://tonetuned.rjscode.com/embed.js\" data-agent=\"k3x9q2m7ab\" async></script>"
}
]
}Every account starts with one example agent (is_example: true). It is read-only and preview-only: you can read it and preview it, but not change or delete it, and it has no public chat link. It does not count toward the plan's agent limit.
POST /agents
Body: name (required, up to 80 characters), preset (optional: friendly, professional, concise, closer, insider; default friendly). Returns 201 and the agent with its full persona. Fails with 403 plan_limit when the plan's agent limit is reached.
curl -X POST https://tonetuned.rjscode.com/api/v1/agents -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" -d '{"name":"Ada","preset":"professional"}'GET /agents/{id}
The agent plus its persona:
{
"id": "6f1c2a9e-3b7d-4c55-9a51-0d1e2f3a4b5c",
"name": "Ada",
"...": "same fields as in the list",
"review_mode": false,
"persona": {
"name": "Ada",
"greeting": "Hi! I'm Ada, an AI assistant. How can I help?",
"sliders": { "friendliness": 70, "detail": 50, "disclosure": 30, "salesPush": 30, "handoff": 40 },
"hardRules": ["promise discounts"],
"handoffText": "I'm not sure about that. Leave your email and a person will get back to you.",
"preset": "friendly",
"accent": "#2d5b46",
"styleGuide": [],
"guidelines": []
}
}PATCH /agents/{id}
Body (all optional, at least one): name, persona (a partial patch merged into the current persona), apply_preset (reset the persona to a preset first, then apply the patch).
Persona fields and limits:
name: assistant display name, 1-60 characters.greeting: first message, up to 300 characters.sliders: each 0-100, send only the ones you change.friendliness(formal and brief .. warm and chatty),detail(one or two sentences .. full walkthrough),disclosure(below 50 = public knowledge only, 50+ = public and customer),salesPush(never pitches .. suggests next steps and asks for an email),handoff(tries to answer .. hands off to a person early).hardRules: up to 20 lines of things the agent must never do, each up to 200 characters, written without "Never" (for example "promise discounts"). This replaces the whole list.handoffText: what the agent says when it hands off to a person, up to 300 characters.accent: widget colour as#rrggbb.styleGuide,guidelines: up to 8 lines each (how the owner writes; how to handle tricky cases).
curl -X PATCH https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" \
-d '{"persona":{"sliders":{"detail":30},"hardRules":["promise discounts","give medical advice"]}}'Returns the updated agent (same shape as GET /agents/{id}).
DELETE /agents/{id}
Deletes the agent with all its knowledge, conversations and leads. Cannot be undone; confirm with the owner first.
curl -X DELETE https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID -H "Authorization: Bearer $TONETUNED_KEY"{ "deleted": true, "id": "6f1c2a9e-3b7d-4c55-9a51-0d1e2f3a4b5c" }Knowledge
Kinds: faq (title = question, content = answer), fact (title = label, content = value, for example "Check-in time" / "from 3 pm"), doc (longer text; title optional; split into passages). Items imported from a web page have kind page (read and edit only). Limits: title up to 300 characters, content up to 200,000 characters. Short, specific FAQs and facts answer best.
GET /agents/{id}/knowledge
Query: limit (1-200, default 50), offset, kind, visibility. Content longer than 2,000 characters is cut (content_truncated: true); fetch the item for the full text. next_offset is null on the last page.
curl "https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/knowledge?limit=50" -H "Authorization: Bearer $TONETUNED_KEY"{
"items": [
{
"id": "0b6d8f6e-7c1a-4d3e-8f2b-1a2b3c4d5e6f",
"kind": "faq",
"title": "Can I bring my dog?",
"content": "Dogs are welcome on request. Tell us before you book.",
"content_length": 54,
"content_truncated": false,
"visibility": "public",
"source_url": null,
"created_at": "2026-10-27T09:15:00Z",
"updated_at": "2026-10-27T09:15:00Z"
}
],
"next_offset": null
}GET /agents/{id}/knowledge/{itemId}
One item with its full content (same item shape).
POST /agents/{id}/knowledge
Add one item, or up to 100 in one request with {"items": [...]} (at most 500,000 content characters per request). visibility defaults to public. Items are live right away. Returns 201.
curl -X POST https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/knowledge -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" -d '{
"items": [
{"kind":"faq","title":"Can I bring my dog?","content":"Dogs are welcome on request. Tell us before you book.","visibility":"public"},
{"kind":"fact","title":"Check-in","content":"from 3 pm, self check-in with a key box","visibility":"public"},
{"kind":"fact","title":"Key box code","content":"4711","visibility":"internal"}
]
}'{
"created": [
{ "id": "0b6d8f6e-7c1a-4d3e-8f2b-1a2b3c4d5e6f", "kind": "faq", "title": "Can I bring my dog?", "visibility": "public" },
{ "id": "...", "kind": "fact", "title": "Check-in", "visibility": "public" },
{ "id": "...", "kind": "fact", "title": "Key box code", "visibility": "internal" }
]
}PATCH /agents/{id}/knowledge/{itemId}
Body: any of title, content, visibility. Returns the full item.
curl -X PATCH https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/knowledge/$ITEM_ID -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" -d '{"visibility":"customer"}'DELETE /agents/{id}/knowledge/{itemId}
curl -X DELETE https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/knowledge/$ITEM_ID -H "Authorization: Bearer $TONETUNED_KEY"{ "deleted": true, "id": "0b6d8f6e-7c1a-4d3e-8f2b-1a2b3c4d5e6f" }Preview (test answers)
POST /agents/{id}/preview
Ask the agent a question exactly as a visitor would. Body: message (required, up to 2,000 characters), history (optional earlier turns, [{"role":"user"|"assistant","content":"..."}], last 8 used), staffView (optional, true also uses internal items). Nothing is saved and it does not use the monthly quota. Limit: 60 previews per hour and 300 per day per account (shared with the dashboard).
curl -X POST https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/preview -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" -d '{"message":"Can I bring my dog?"}'{
"answer": "Yes, dogs are welcome on request. Please tell us before you book.",
"confidence": "high",
"handoff": false,
"askEmail": false,
"sources": [
{ "id": "c1d2e3f4-...", "title": "Can I bring my dog?", "visibility": "public", "snippet": "Q: Can I bring my dog?\nA: Dogs are welcome on request..." }
]
}confidence: high = the knowledge clearly answers; low = partly; none = it does not, and the agent hands off. A none answer means knowledge is missing: add an item and preview again.
Conversations, leads, unanswered questions
GET /agents/{id}/conversations
Most recent first, each with its messages oldest first (up to 100). Query: limit (1-50, default 20), offset, needs_human (true/false).
curl "https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/conversations?limit=20" -H "Authorization: Bearer $TONETUNED_KEY"{
"conversations": [
{
"id": "9e8d7c6b-...",
"started_at": "2026-10-27T10:00:00Z",
"last_at": "2026-10-27T10:02:00Z",
"needs_human": false,
"is_lead": true,
"lead_email": "guest@example.com",
"review_status": "pending",
"messages": [
{ "id": "...", "role": "visitor", "content": "Do you have parking?", "confidence": null, "created_at": "2026-10-27T10:00:00Z" },
{ "id": "...", "role": "assistant", "content": "Yes, free private parking next to the house.", "confidence": "high", "created_at": "2026-10-27T10:00:02Z" }
]
}
],
"next_offset": null
}GET /agents/{id}/leads
Conversations where the visitor left an email or showed buying intent. Query: limit (1-200, default 50), offset.
{
"leads": [
{ "conversation_id": "9e8d7c6b-...", "email": "guest@example.com", "buying_intent": true, "needs_human": false, "started_at": "...", "last_at": "..." }
],
"next_offset": null
}GET /agents/{id}/unanswered
Visitor questions the agent could not answer (newest first). Query: limit (1-100, default 50).
{
"unanswered": [
{ "message_id": "5a4b3c2d-...", "conversation_id": "9e8d7c6b-...", "question": "Is there a sauna?", "reply": "I don't have that information...", "created_at": "..." }
]
}POST /agents/{id}/unanswered/{messageId}
Answer it: creates an FAQ item (question = the visitor's question unless you send question) and removes it from the list. Body: answer (required), question, visibility (default public). Returns 201 {"item_id": "...", "resolved": true}.
curl -X POST https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/unanswered/$MESSAGE_ID -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" -d '{"answer":"No sauna, but the lake is a 2-minute walk."}'DELETE /agents/{id}/unanswered/{messageId}
Dismiss without adding knowledge. Returns {"resolved": true, "message_id": "..."}.
Imports and the disclosure policy
An import reads the owner's files and web pages, sorts every detail into a visibility level, and flags details that look private. Nothing goes live until the owner accepts the resulting disclosure policy. Use an import (not POST /knowledge) for the owner's real documents.
Paid plans include 2 imports a month (an import counts when its first source is added). Free has none yet: start_import returns 403 upgrade_required. Up to 100 sources and about 3 MB of text per import.
The flow:
POST /agents/{id}/imports: start (or get the open import). Returns the import withidandallowance.POST /imports/{importId}/sourcesonce per file or page:
- {"kind": "text", "name": "house-rules.md", "text": "..."} for any file you can read as text - {"kind": "url", "url": "https://example.com/faq"} for one web page - {"kind": "pdf", "name": "menu.pdf", "base64": "..."} for a PDF (about 1.4 MB at most through the API) Returns {"source", "added", "flagged", "removed"}. Passwords, API keys, card and bank numbers are dropped (removed) and never stored.
POST /imports/{importId}/finish: reading is done. Returns the import withitemsandopen_decisions.- For every item with
needs_decision: true, ask the owner itsquestionand send their answer:PATCH /imports/{importId}/items/{itemId}with{"visibility": "public" | "customer" | "internal"}. Do not decide private details yourself. You may also change any other item's level, or skip it with{"leave_out": true}. - Ask the owner whether there is anything else the assistant must never talk about, and send it:
POST /imports/{importId}/ruleswith{"rules": ["discuss other guests"]}(each rule completes "The assistant must never ..."; send[]for none). GET /imports/{importId}/policy: show the owner the draft (shared_with_anyone,only_if_asked,never_shared,never_discuss) or send thempolicy_url, which has Save as PDF.- Only after the owner says they accept:
POST /imports/{importId}/acceptwith{"confirm": true}. The items go live, the never-discuss rules join the persona, and the policy is stored as the next version. Refused (409undecided) while an item still needs a decision.
Later imports apply earlier decisions automatically: a detail of a kind the owner already decided (for example door codes) is filed without asking again.
Other calls:
GET /imports/{importId}: the import, its items andnext_question.DELETE /imports/{importId}: cancel an import that is not live (a used monthly import is not given back).GET /agents/{id}/policy(optional?version=2): the accepted policy and the list ofversions.
only_if_asked_held_back: true means the agent's disclosure slider is below 50, so "only if asked" items are not shared at all until the owner raises it.
curl -X POST https://tonetuned.rjscode.com/api/v1/agents/$AGENT_ID/imports -H "Authorization: Bearer $TONETUNED_KEY"
curl -X POST https://tonetuned.rjscode.com/api/v1/imports/$IMPORT_ID/sources -H "Authorization: Bearer $TONETUNED_KEY" -H "Content-Type: application/json" \
-d '{"kind":"text","name":"faq.md","text":"Do you allow pets?\nSmall dogs are welcome for 10 EUR a night."}'MCP server
The same actions are available as MCP tools at:
https://tonetuned.rjscode.com/api/mcpStreamable HTTP, stateless, JSON responses (protocol versions 2025-06-18 and 2025-03-26). Same Bearer key.
Tools: get_account, update_account, list_agents, create_agent, get_agent, update_agent, delete_agent, list_knowledge, get_knowledge, add_knowledge, update_knowledge, delete_knowledge, preview_chat, list_conversations, list_leads, list_unanswered, answer_unanswered, dismiss_unanswered, start_import, add_import_source, finish_import_reading, get_import, decide_import_item, add_import_rules, get_import_policy, accept_import_policy, cancel_import, get_policy. Tool arguments use the REST field names, with agent_id, import_id, item_id and message_id in place of path ids; add_knowledge always takes items.
Claude Code:
claude mcp add --transport http tonetuned https://tonetuned.rjscode.com/api/mcp --header "Authorization: Bearer tt_live_YOUR_KEY"Cursor (~/.cursor/mcp.json) and other clients that take a JSON config:
{
"mcpServers": {
"tonetuned": {
"url": "https://tonetuned.rjscode.com/api/mcp",
"headers": { "Authorization": "Bearer tt_live_YOUR_KEY" }
}
}
}Claude Desktop (claude_desktop_config.json, through the mcp-remote bridge):
{
"mcpServers": {
"tonetuned": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://tonetuned.rjscode.com/api/mcp", "--header", "Authorization: Bearer tt_live_YOUR_KEY"]
}
}
}Rate limits
- 120 requests per minute per API key (REST and MCP together).
- Preview: 60 per hour and 300 per day per account, shared with the dashboard.
- Over a limit you get 429
rate_limited. Wait and retry; do not loop.
Errors
Every error has the same shape and a matching HTTP status:
{ "error": { "code": "not_found", "message": "Agent not found." } }- 400
bad_request(the message names the field),invalid_json - 401
unauthorized: missing, malformed or revoked key - 403
plan_limit(agent limit of the plan),example_read_only(example agents cannot be changed),upgrade_required(imports need a paid plan) - 409
undecided(an import item still needs a decision),not_in_review,import_closed,import_done - 422
fetch_failed,no_text,import_fulland source checks such astoo_shortorbad_pdf - 404
not_found: no such id in this account (ids of other accounts look the same as missing ones) - 413
payload_too_large - 429
rate_limited;limit_reached(this month's included imports are used) - 500
server_error, 503unavailable: retry later
In MCP, a failed tool call returns a result with isError: true and the code and message as text.
Coming next
- Past-conversation import (learning the owner's writing style) through the API; the owner does it in the dashboard for now.
- Paid extra imports, priced by size.