now in beta

api & mcp server

connect hold your voice to your automations, ai agents, n8n workflows, and ci pipelines. pipe any document through voice analysis without opening a browser tab.

rest api mcp server n8n · claude desktop · zapier · cursor

generate your api key

api keys live in the app under settings → api & mcp. you get up to 10 active keys per account — name them by integration so you can revoke individual ones without breaking everything else.

key format

all keys start with hyv_live_ followed by 48 lowercase hex characters. never commit keys to git — use environment variables.

one-time reveal

the full key is shown once at creation time. copy it immediately — we only store the hash. if you lose it, delete and recreate.

authenticate every request
Authorization: Bearer hyv_live_<your-key>
quick test
curl https://holdyourvoice.com/v1/usage \
  -H "Authorization: Bearer hyv_live_<your-key>"
the api is available on solo, team, and byok plans. all api calls share your monthly document quota with the web app. trials can view the docs but cannot generate keys until upgraded.

endpoints

base url: https://holdyourvoice.com — all v1 endpoints require the authorization header above. responses are json. errors return {"error": "...", "error_code": "..."} with the appropriate http status code.

endpoint what it does quota?
POST /v1/analyzeanalyze text against a voice profile — returns score, highlights, per-signal breakdown, and a cost blockunmetered
POST /v1/rewriterewrite a flagged passage to fix a specific drift signalunmetered
GET /v1/profileslist voice profiles you can access (own + team-shared)free
GET /v1/usagecurrent month quota, limits, and per-brand 30-day token breakdownfree
GET /v1/contextslash-command context: plan, byok billing, profiles, recent documents, and team/byok user rollupsfree
GET /v1/documentslist documents (paginated by limit and offset)free
POST /v1/documentscreate a new document1 slot
GET /v1/documents/:idfetch a single document by idfree
PUT /v1/documents/:idupdate title or content of an existing document (a substantial content swap charges 1 slot — see abuse guard below)free*

summon a specific brand voice

every endpoint that takes a voice profile accepts any of these identifier forms on a brand, profile, or profile_id field — matched in this precedence:

formexamplematching
uuid"550e8400-e29b-41d4-a716-446655440000"exact — canonical id
slug"acme-co"exact, lowercase kebab-case
name"Acme Co"exact, case-insensitive, unicode-normalised
business"Acme Corporation"exact, case-insensitive
partial"acme"substring match (3+ chars) on name or business

if a fuzzy query matches more than one profile, the api returns 400 with the candidate list so your tool can disambiguate instead of guessing.

ambiguous match → 400
{
  "error": "Profile query 'Growth' is ambiguous — matched 2 profiles...",
  "error_code": "profile_ambiguous",
  "query": "Growth",
  "candidates": [
    { "id": "...", "name": "Growth Inc",  "business": "Growth Inc", "slug": "growth-a" },
    { "id": "...", "name": "Growth Labs", "business": "Growth Labs", "slug": "growth-b" }
  ]
}
omit the brand field entirely to use your default profile. on team plans, the default can be a team-shared profile so teammates inherit the same voice without cloning it.

analyze text for voice drift

pass any text and optionally a brand. get back a drift score, flagged highlights, remaining quota, and a cost block so you can see whether the call was billed to our platform or your own provider key (byok).

request — summon a brand by name
curl -X POST https://holdyourvoice.com/v1/analyze \
  -H "Authorization: Bearer hyv_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "our platform leverages best-in-class ai to deliver robust synergies",
    "brand": "Acme Co"
  }'
response
{
  "highlights": [
    { "text": "leverages best-in-class ai", "signal": "corporate_filler", "severity": "high", "suggestion": "try: uses ai" },
    { "text": "robust synergies",          "signal": "buzzword_stacking", "severity": "high", "suggestion": "try: real results" }
  ],
  "signal_score_delta":   { "corporate_filler": -28, "buzzword_stacking": -30 },
  "trust_tools_present":  [],
  "trust_tools_missing":  [ "specific_numbers", "attributed_quote" ],
  "profile_id": "550e8400-e29b-41d4-a716-446655440000",
  "remaining": { "documents": 47, "rewrites": null },
  "cost": {
    "paid_by": "hyv_platform",
    "model": "claude-haiku-4-5-20251001",
    "input_tokens": 420,
    "output_tokens": 180
  }
}
paid_by is "hyv_platform" when we route through opencode zen with our key, or "your_anthropic_key" / "your_openrouter_key" / "your_opencode_key" when you've enabled byok. token counts come straight from the provider response so you can estimate exact spend.

rewrite a flagged passage

takes the original sentence and a specific drift signal. returns a rewrite that fixes that signal while preserving meaning. uses the same brand-resolution rules.

request
curl -X POST https://holdyourvoice.com/v1/rewrite \
  -H "Authorization: Bearer hyv_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "leverages best-in-class ai to deliver robust synergies",
    "issue": "corporate_filler",
    "brand": "acme-co"
  }'
response
{
  "rewritten": "uses ai to get real results",
  "original":  "leverages best-in-class ai to deliver robust synergies",
  "profile_id": "550e8400-e29b-41d4-a716-446655440000",
  "remaining": { "documents": 47, "rewrites": null },
  "cost": {
    "paid_by": "hyv_platform",
    "model": "claude-haiku-4-5-20251001",
    "input_tokens": 180,
    "output_tokens": 95
  }
}

list voice profiles

returns every profile you can access — your own and any shared with your team. each row carries an ownership marker so your tool can distinguish them.

request
curl https://holdyourvoice.com/v1/profiles \
  -H "Authorization: Bearer hyv_live_<your-key>"
response
{
  "profiles": [
    {
      "id":          "550e8400-...",
      "name":        "Acme Co",
      "business":    "Acme Corporation",
      "slug":        "acme-co",
      "industry":    "saas",
      "voice_keywords":    [ "direct", "specific", "first-person" ],
      "voice_descriptions": { "direct": "...", "specific": "..." },
      "is_default":  true,
      "ownership":   "own",
      "team_id":     null,
      "created_at":  "2026-02-10T12:00:00Z",
      "updated_at":  "2026-04-18T09:14:22Z"
    },
    {
      "id":         "6ba7b810-...",
      "name":       "Shared Brand",
      "business":   "Shared Brand LLC",
      "slug":       "shared-brand",
      "ownership":  "team",
      "team_id":    "team-acme-0000",
      "is_default": false
      /* ... */
    }
  ]
}

usage — with per-brand breakdown

the by_brand_30d array lets agencies managing many brands see which one is consuming what. null-profile rows land in the unattributed bucket for legacy calls that predate brand tagging.

{
  "plan":          "team",
  "plan_active":   true,
  "is_byok":       false,
  "token_billing": "hyv_platform",
  "period":        "2026-04",
  "documents_used": 12,
  "rewrites_used":  0,
  "remaining":     { "documents": 288, "rewrites": null },
  "limits": {
    "documents": 300,
    "rewrites":  null,
    "rpm":       60
  },
  "quota_model": {
    "document_create": "counted_monthly",
    "analyze":         "unmetered",
    "rewrite":         "unmetered_monthly_legacy"
  },
  "by_brand_30d": [
    { "profile_id": "550e...", "name": "Acme Co",       "slug": "acme-co",       "scope": "own",          "analyses": 47, "rewrites": 18, "input_tokens": 32100, "output_tokens": 9200 },
    { "profile_id": "6ba7...", "name": "Shared Brand",  "slug": "shared-brand",  "scope": "team",         "analyses": 22, "rewrites":  5, "input_tokens": 14500, "output_tokens": 3400 },
    { "profile_id": null,      "name": null,            "slug": null,            "scope": "unattributed", "analyses":  3, "rewrites":  0, "input_tokens":  1900, "output_tokens":  400 }
  ]
}
with byok enabled, the response shifts: "is_byok": true, "token_billing": "your_<provider>_account", and quota_model.rewrite becomes "unmetered" (dropping the legacy monthly counter).

create and update documents

documents created via the api appear in your hold your voice editor. create uses 1 monthly slot. updates are free unless the new content is substantially different from the last counted version — see the abuse guard note below.

create a document against a brand
curl -X POST https://holdyourvoice.com/v1/documents \
  -H "Authorization: Bearer hyv_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "title":   "q2 product announcement",
    "content": "your draft text here...",
    "brand":   "Acme Co"
  }'
update content
curl -X PUT https://holdyourvoice.com/v1/documents/doc_abc123 \
  -H "Authorization: Bearer hyv_live_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{ "content": "updated draft..." }'
abuse guard (content-swap detection): a put that replaces the body with substantially different content is treated as a new document and charges 1 slot. the test is strict: if the new content keeps under 50% of the baseline's words AND both sides are at least 25 words AND the trigram overlap with the baseline is under 15%, the guard fires. anything short of that — typing, editing, restructuring, rephrasing, or keeping at least half the vocabulary — stays free. the response includes _quota_charged: true whenever a slot was consumed so your automation knows.

error codes

statuserror_codemeaning
401missing or invalid api key
402plan inactive (none / expired) — renew to resume api access
403scope denied (e.g. key without rewrite scope calling /v1/rewrite)
400profile_ambiguousbrand query matched more than one profile — see candidates array
404profile_not_foundthe brand query didn't resolve in your workspace
429rpm exceeded (60 solo/team, 120 byok) or monthly document quota exhausted
429submission_quota_exceededa put hit the abuse guard and you have no remaining document slots — upgrade plan or edit existing doc
500server error — retry with exponential backoff

copy this prompt to your agent

v1 onboarding is intentionally simple: upgrade to a paid plan, create a scoped key under settings → api & mcp, then paste the prompt below into codex, cursor, or another local agent. the agent installs the mcp server and verifies the connection for you.

api and mcp keys require an active paid plan. trials can read this page and preview the tools, but they cannot create keys until upgraded.
set up hold your voice mcp in this local agent environment.

context:
- hold your voice api and mcp access require an active paid plan. this key was created from a paid account.
- the public setup contract is https://holdyourvoice.com/.well-known/agent-onboarding
- the mcp server card is https://holdyourvoice.com/.well-known/mcp/server-card.json

requirements:
1. use this api key as HYV_API_KEY: <paste-your-api-key-here>
2. do not print the key back to me, commit it, or store it in a shared project file.
3. download https://holdyourvoice.com/mcp_server.py or reuse an existing local copy if it is already present.
4. configure an mcp server named hyv with command "python3" and args pointing at the local mcp_server.py file.
5. set HYV_BASE_URL to https://holdyourvoice.com unless i explicitly gave you another endpoint.
6. install /hyv-status from https://holdyourvoice.com/.well-known/agent-commands/hyv-status.md if your agent supports custom slash commands.
7. verify the setup by calling hyv_get_context, hyv_get_usage, and hyv_list_profiles.
8. after verification, use /hyv-status for account context, use hyv_analyze before hyv_rewrite, use brand names or slugs when a brand is mentioned, and ask before overwriting documents.

expected result:
- tell me where you placed the mcp server file.
- tell me which mcp config you updated.
- confirm the usage and profile tools worked.
- do not include the raw api key in your final response.

use hyv inside claude desktop, cursor & more

the mcp server lets any mcp-compatible ai assistant call hold your voice tools directly — mid-conversation, no copy-paste. same quota rules as the rest api.

step 1 — download the server script
curl -O https://holdyourvoice.com/mcp_server.py
step 2 — add to claude_desktop_config.json
{
  "mcpServers": {
    "hyv": {
      "command": "python3",
      "args": ["/path/to/mcp_server.py"],
      "env": {
        "HYV_API_KEY": "hyv_live_..."
      }
    }
  }
}

replace /path/to/mcp_server.py with the actual path where you downloaded the file. restart claude desktop after saving.

available tools
tool namewhat it does
hyv_analyzeanalyze text against a voice profile. takes brand (name/slug/uuid)
hyv_rewriterewrite a flagged passage to fix a specific drift signal
hyv_resolve_brandpreview which profile a brand query resolves to. returns {matched, via, candidates}via is one of id / slug / name / business / partial / ambiguous_name / ambiguous_business / ambiguous_partial / not_found
hyv_list_profileslist all voice profiles the key can access — own + team-shared, each marked with ownership
hyv_get_usagequota, limits, and per-brand 30-day token breakdown
hyv_get_contextone-call context for /hyv-status: plan, byok billing, profiles, recent documents, per-profile counts, and team/byok per-user rollups
hyv_list_documentslist documents (paginated)
hyv_get_documentfetch a single document by id
hyv_create_documentcreate a new document. takes brand to tie the doc to a voice (uses 1 quota slot)
hyv_update_documentupdate title or content of an existing document
every tool that takes a voice profile accepts the brand name, business name, slug, or uuid on a brand argument — fuzzy, case-insensitive. if your tool isn't sure which brand a user meant, call hyv_resolve_brand first to preview the match and surface a confirmation.

slash command

agents that support custom commands can install the predefined context command:

/hyv-status

source: https://holdyourvoice.com/.well-known/agent-commands/hyv-status.md

what it does:
- calls hyv_get_context
- shows plan, byok billing, quota, profiles, and recent documents
- shows per-user rollups for team and byok accounts
- keeps raw api keys and secrets out of the response

claude conversation example

once the mcp server is wired up, claude can run voice checks mid-conversation:

user:   analyze this draft against our acme voice and fix anything off-brand

claude: → hyv_resolve_brand(brand="acme")
        ← { "matched": { "name": "Acme Co", "slug": "acme-co", "id": "550e..." }, "via": "partial" }

claude: → hyv_analyze(text="...", brand="acme-co")
        ← { "highlights": [...], "cost": { "paid_by": "hyv_platform", "input_tokens": 420, ... } }

claude: → hyv_rewrite(text="leverages best-in-class ai", issue="corporate_filler", brand="acme-co")
        ← { "rewritten": "uses ai", "cost": { ... } }

claude: here's your draft with 3 fixes for the acme voice: ...

example automation workflow

what a complete agent loop looks like — draft, check, fix, ship:

# your agent in python, n8n, or any tool that supports http

import requests

key = "hyv_live_..."
headers = {"Authorization": f"Bearer {key}"}
base = "https://holdyourvoice.com"

# 1. agent writes a draft
draft = llm.generate(brief)

# 2. create the document, tagged with the right brand
doc = requests.post(f"{base}/v1/documents",
  headers=headers,
  json={"title": "q2 announcement", "content": draft, "brand": "Acme Co"}
).json()

# 3. analyze against the same brand
result = requests.post(f"{base}/v1/analyze",
  headers=headers,
  json={"text": draft, "brand": "Acme Co"}
).json()

print(f"tokens used: {result['cost']['input_tokens']} in / "
      f"{result['cost']['output_tokens']} out — paid by {result['cost']['paid_by']}")

# 4. rewrite any high-severity flags against the same brand
for flag in result["highlights"]:
  if flag["severity"] == "high":
    fix = requests.post(f"{base}/v1/rewrite",
      headers=headers,
      json={"text": flag["text"], "issue": flag["signal"], "brand": "Acme Co"}
    ).json()
    draft = draft.replace(flag["text"], fix["rewritten"])

# 5. update document with clean version
requests.put(f"{base}/v1/documents/{doc['id']}",
  headers=headers,
  json={"content": draft}
)

# 6. check per-brand spend at end of run
usage = requests.get(f"{base}/v1/usage", headers=headers).json()
for brand in usage["by_brand_30d"]:
  print(f"{brand['name'] or 'unattributed'}: "
        f"{brand['analyses']} analyses + {brand['rewrites']} rewrites "
        f"({brand['input_tokens']}+{brand['output_tokens']} tokens)")

how usage is counted

the api shares your monthly quota with the web app. documents is the only meter that caps your plan. analyses and rewrites done against a document are included in that document's slot.

documents

every document created — via the web app or the api — counts against your monthly quota. solo 50, team 300, byok unlimited. resets on the 1st of each month (utc).

analysis & rewrites

unmetered on every active plan. running POST /v1/analyze or POST /v1/rewrite on a document you've already created doesn't consume a second slot. a call against 5 different brands on the same draft = zero extra documents used.

rate limit (rpm)

60 req/min on solo and team. 120 req/min on byok. per-account across all your keys combined. exceeding returns 429 with a retry_after value — the window resets every 60 seconds.

team quota sharing

on team plans, the 300-doc/month quota is shared across all members. api keys issued by any teammate draw from the same pool. voice profiles marked with team_id are visible to every team member — no need to re-create them per seat.

who pays for tokens

on solo / team / byok, analyze + rewrite calls route through our opencode zen platform account. on byok with byok enabled, your own provider key is used and you pay that provider directly — our token_usage table still logs the call for your reference but no charge routes through us. every response carries a cost.paid_by field so your automation knows which.

byok (byok only)

bring-your-own-key is exclusive to byok. solo and team users can't enable byok — the plan price includes typical platform ai usage. for high-volume workloads, upgrade to byok and configure byok under settings → api & mcp.

voice profiles per plan

solo: 1 voice profile. team: 4 profiles shared across all team members (each doc can be tied to one). byok: unlimited. team-shared profiles carry a team_id so teammates see and can analyse against them without cloning.

abuse guard (content-swap detection): updating an existing document with substantially different content — less than 50% word retention from the last counted version on a draft ≥25 words — is treated as a new piece of writing and uses 1 quota slot. typing, drafting, minor edits, rephrasing, short-form writing under 25 words, and identical re-pastes are always free. the response includes _quota_charged: true whenever a slot was consumed.

well-known endpoints

hold your voice publishes standard discovery documents so ai agents and tooling can find and configure the api automatically.

urlstandard
/.well-known/api-catalogietf api catalog (rfc draft)
/.well-known/agent-onboardingcopy-prompt setup contract for paid users connecting local agents
/.well-known/agent-commands/hyv-status.mdpredefined slash-command prompt for account context
/.well-known/mcp/server-card.jsonmcp server card
/.well-known/oauth-protected-resourcerfc 9728 — protected resource metadata
/.well-known/oauth-authorization-serverrfc 8414 — authorization server metadata
/.well-known/agent-skills/index.jsonagentskills.io skills index
solo plan — $19/mo · 50 docs

set up your first integration in 5 minutes

start a solo plan, generate an api key, and connect your first workflow. voice analysis and rewrites are unlimited — you only count document creation against your quota.

start free trial →
3-day trial → cancel any time