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_KEY

The 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_KEY

Base URL

https://tonetuned.rjscode.com/api/v1

JSON in, JSON out. Send Content-Type: application/json with a body. Ids are UUIDs.

Quick start for agents

  1. GET /agents to find the agent (or POST /agents to create one).
  2. 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.
  3. PATCH /agents/{id} to tune the voice.
  4. POST /agents/{id}/preview with a few real customer questions, and fix gaps until the answers are right.
  5. Give the owner the agent's chat_url or embed_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:

  1. POST /agents/{id}/imports: start (or get the open import). Returns the import with id and allowance.
  2. POST /imports/{importId}/sources once 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.

  1. POST /imports/{importId}/finish: reading is done. Returns the import with items and open_decisions.
  2. For every item with needs_decision: true, ask the owner its question and 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}.
  3. Ask the owner whether there is anything else the assistant must never talk about, and send it: POST /imports/{importId}/rules with {"rules": ["discuss other guests"]} (each rule completes "The assistant must never ..."; send [] for none).
  4. GET /imports/{importId}/policy: show the owner the draft (shared_with_anyone, only_if_asked, never_shared, never_discuss) or send them policy_url, which has Save as PDF.
  5. Only after the owner says they accept: POST /imports/{importId}/accept with {"confirm": true}. The items go live, the never-discuss rules join the persona, and the policy is stored as the next version. Refused (409 undecided) 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 and next_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 of versions.

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/mcp

Streamable 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_full and source checks such as too_short or bad_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, 503 unavailable: 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.