API Documentation

Build voice profiles from writing samples and generate messages that sound like you, not like AI.

Endpoints

Quickstart

Go from zero to drafting in your voice in three steps.

1

Get an API key

Sign up at tonos.fyi/register, then create a key at Settings. Your key starts with tnos_live_.

2

Build a voice profile

Submit at least 3 writing samples. More samples = higher confidence.

curl
curl -X POST https://tonos.fyi/profile \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "samples": [
      {"text": "hey just checking in on that proposal, lmk if you need anything"},
      {"text": "sounds good, i can hop on a call tomorrow if easier"},
      {"text": "perfect, will send over the updated deck by end of day"}
    ],
    "platform": "slack"
  }'
3

Draft a message

Use the profile ID from step 2 to generate messages in your voice.

curl
curl -X POST "https://tonos.fyi/generate/draft?stream=false" \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profile_id": "PROFILE_ID_FROM_STEP_2",
    "recipient": "Sarah",
    "purpose": "Follow up on the partnership proposal",
    "key_points": ["Check if she reviewed it", "Offer to hop on a call"]
  }'

Authentication

All API requests require a Bearer token in the Authorization header.

Authorization: Bearer tnos_live_YOUR_KEY

Getting a key

Create an API key from the Settings page in the web UI, or use the POST /keys endpoint with an existing key. Keys use the tnos_live_ prefix. The full key is only shown once at creation -- store it securely.

OAuth tokens

MCP clients (Claude Code, Cursor) authenticate via OAuth and receive tnos_oauth_ tokens automatically. The OAuth flow is handled by the MCP client -- no manual setup needed.

Rate limits: 100 requests per minute per IP, 200 requests per minute per key. Exceeding returns 429 with a RATE_LIMITED error.

Credit Costs

Each operation deducts credits based on the model tier used. Trial accounts start with 45 free credits.

OperationHaikuSonnetOpus
Profile build35100175
Draft / Rewrite135
PlanCredits / monthModels availableDefault model
Trial45HaikuHaiku
Starter ($9/mo)500Haiku, SonnetHaiku
Pro ($24/mo)1,500Haiku, SonnetSonnet
Business ($49/mo)4,000Haiku, Sonnet, OpusSonnet

Keys

Manage API keys for programmatic access.

POST/keys

Create a new API key. The plaintext key is only returned once.

FieldTypeDescription
label requiredstringHuman-readable label (max 100 chars)
Request
curl -X POST https://tonos.fyi/keys \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "production-server"}'
Response 201
{
  "id": "key_abc123",
  "plaintext": "tnos_live_abc123def456...",
  "prefix": "tnos_live_abc1"
}
GET/keys

List all API keys for your account. Keys are returned without the plaintext value.

Request
curl https://tonos.fyi/keys \
  -H "Authorization: Bearer tnos_live_YOUR_KEY"
Response 200
{
  "keys": [
    {
      "id": "key_abc123",
      "key_prefix": "tnos_live_abc1",
      "label": "production-server",
      "last_used_at": "2026-04-01T12:00:00Z",
      "revoked": false,
      "created_at": "2026-03-15T08:30:00Z"
    }
  ]
}
DELETE/keys/:id

Permanently revoke an API key. The key stops working immediately.

Request
curl -X DELETE https://tonos.fyi/keys/key_abc123 \
  -H "Authorization: Bearer tnos_live_YOUR_KEY"
Response 200
{"revoked": true}
GET/keys/:id/usage

Get usage summary for a specific API key.

Request
curl https://tonos.fyi/keys/key_abc123/usage \
  -H "Authorization: Bearer tnos_live_YOUR_KEY"
Response 200
{
  "total_calls": 142,
  "endpoints": [
    {"endpoint": "generation", "call_count": 98},
    {"endpoint": "extraction:profile", "call_count": 44}
  ]
}

Profiles

Voice profiles are built from writing samples. More samples and more variety produce higher confidence.

POST/profile

Create a new voice profile from writing samples. Requires at least 3 samples.

FieldTypeDescription
samples requiredarrayArray of writing samples (min 3). Each has text (required), platform, sample_date, context.
platformstringDefault platform context for all samples (e.g., "slack", "email")
Request
curl -X POST https://tonos.fyi/profile \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "samples": [
      {"text": "hey just checking in, lmk if you need anything from my end"},
      {"text": "sounds good, i can hop on a call tomorrow if easier"},
      {"text": "perfect, will send over the updated deck by eod. no rush"}
    ]
  }'
Response 201
{
  "profile_id": "prof_abc123",
  "voice": {
    "formality": {"value": 2.1, "confidence": 0.85},
    "directness": {"value": 8.6, "confidence": 0.90},
    "warmth": {"value": 7.4, "confidence": 0.82},
    "summary": "Casual, direct communicator who defaults to lowercase...",
    ...
  },
  "confidence": 0.78,
  "confidence_label": "high",
  "tokens_used": 1847
}
GET/profile/:id

Retrieve the active version of a voice profile.

Request
curl https://tonos.fyi/profile/prof_abc123 \
  -H "Authorization: Bearer tnos_live_YOUR_KEY"
Response 200
{
  "profile_id": "prof_abc123",
  "voice": { ... },
  "confidence": 0.78,
  "confidence_label": "high",
  "version": 2,
  "extracted_at": "2026-04-01T12:00:00Z"
}
DELETE/profile/:id

Delete a voice profile and all associated data. This is permanent.

Request
curl -X DELETE https://tonos.fyi/profile/prof_abc123 \
  -H "Authorization: Bearer tnos_live_YOUR_KEY"
Response 200
{"deleted": true}
POST/profile/:id/samples

Add new writing samples to an existing profile and re-extract. Requires at least 3 total samples.

FieldTypeDescription
samples requiredarrayNew samples to add (min 1). Each has text (required), platform, sample_date, context.
platformstringDefault platform context for new samples
Request
curl -X POST https://tonos.fyi/profile/prof_abc123/samples \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "samples": [
      {"text": "thanks for the quick turnaround on this, really appreciate it"},
      {"text": "just a heads up, i might be a few minutes late to the standup"}
    ]
  }'
Response 200
{
  "profile_id": "prof_abc123",
  "voice": { ... },
  "confidence": 0.84,
  "confidence_label": "high",
  "tokens_used": 2105
}

Generation

Draft new messages or rewrite existing text in your voice. Both endpoints support SSE streaming (default) or non-streaming mode.

Streaming: By default, generation endpoints return an SSE event stream. Text chunks arrive as data: events, followed by an event: metadata event with profile_id and tokens_used, then data: [DONE]. To get a single JSON response instead, add ?stream=false.
POST/generate/draft

Generate a new message in the voice of the specified profile.

FieldTypeDescription
profile_id requiredstringVoice profile ID
recipient requiredstringWho the message is for (max 500 chars)
purpose requiredstringWhat the message should accomplish (max 5,000 chars)
key_points requiredstring[]Key points to include (1-10 items, each max 5,000 chars)
platformstringPlatform voice mode: imessage, slack, linkedin, email, etc.
Request (non-streaming)
curl -X POST "https://tonos.fyi/generate/draft?stream=false" \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profile_id": "prof_abc123",
    "recipient": "Sarah",
    "purpose": "Follow up on partnership proposal",
    "key_points": [
      "Check if she reviewed the proposal",
      "Offer to hop on a call to discuss"
    ],
    "platform": "email"
  }'
Response 200 (non-streaming)
{
  "text": "hey sarah, wanted to circle back on the proposal i sent over last week. no pressure at all, just wondering if you've had a chance to look through it. happy to jump on a quick call if that's easier. let me know",
  "profile_id": "prof_abc123",
  "tokens_used": 312
}
POST/generate/rewrite

Rewrite source text to match the voice of the specified profile. Preserves meaning, transforms style.

FieldTypeDescription
profile_id requiredstringVoice profile ID
source_text requiredstringText to rewrite (max 10,000 chars)
platformstringPlatform voice mode: imessage, slack, linkedin, email, etc.
Request (non-streaming)
curl -X POST "https://tonos.fyi/generate/rewrite?stream=false" \
  -H "Authorization: Bearer tnos_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profile_id": "prof_abc123",
    "source_text": "Dear Sarah, I hope this email finds you well. I wanted to follow up regarding our previous conversation about the partnership opportunity. Please let me know your thoughts at your earliest convenience.",
    "platform": "email"
  }'
Response 200 (non-streaming)
{
  "text": "hey sarah, just circling back on the partnership thing we talked about. would love to hear what you're thinking whenever you get a chance. no rush",
  "profile_id": "prof_abc123",
  "tokens_used": 287
}

MCP Integration

Tonos exposes all tools via the Model Context Protocol (MCP) over Streamable HTTP at /mcp. Connect from any MCP-compatible client.

Claude Code

Add Tonos as an MCP server
claude mcp add tonos \
  --transport http \
  --url https://tonos.fyi/mcp

Claude Code handles OAuth automatically. On first use, it opens a browser for login, then caches the token.

Cursor

Add to your .cursor/mcp.json:

.cursor/mcp.json
{
  "mcpServers": {
    "tonos": {
      "url": "https://tonos.fyi/mcp"
    }
  }
}

Available MCP tools

ToolDescriptionCredits
tonos_submit_samplesStore writing samples for extractionFree
tonos_generate_profileExtract voice profile from stored samples35-175
tonos_clear_samplesDelete all stored samplesFree
tonos_get_profileGet current voice profileFree
tonos_export_profileExport profile as JSON or MarkdownFree
tonos_delete_profilePermanently delete profile and all dataFree
tonos_draft_messageGenerate a new message in your voice1-5
tonos_rewrite_textRewrite text to match your voice1-5
tonos_submit_feedbackRate or correct a generated draftFree

Error Codes

All errors return a consistent JSON structure with code, message, and optional hint.

Error response format
{
  "code": "VALIDATION_ERROR",
  "message": "At least 3 samples are required",
  "hint": "Provide an array of at least 3 objects, each with a non-empty \"text\" field"
}
CodeHTTPMeaning
AUTH_REQUIRED401No Authorization header provided
AUTH_INVALID401API key is invalid, revoked, or expired
AUTH_FAILED401Authentication check failed
RATE_LIMITED429Too many requests (IP or key limit)
CREDITS_INSUFFICIENT402Not enough credits for this operation
MODEL_NOT_AVAILABLE403Requested model not available on your plan
VALIDATION_ERROR400Invalid request body or parameters
NOT_FOUND404Resource does not exist or is not owned by your account
EXTRACTION_ERROR502Voice extraction failed (upstream LLM error)
GENERATION_ERROR502Text generation failed (upstream LLM error)
INTERNAL_ERROR500Unexpected server error

Base URL

EnvironmentURL
Productionhttps://tonos.fyi
Local devhttp://localhost:3003