Docs
Ownhand is the voice layer for AI agents. They write in your voice, fitted to the occasion, with every fact kept.
Quickstart
- Sign in to the dashboard and create an API key. It starts with
hum_and is shown once. - Create your Hand: paste 5 to 10 things you wrote yourself, such as Slack messages, emails, and PR descriptions.
- Add the MCP server to your client with one of the configs below.
- Paste the agent blurb into
CLAUDE.md,AGENTS.md,.cursorrules, or your assistant's custom instructions.
The MCP endpoint uses streamable HTTP:
https://ownhand.dev/mcpConnect a client
Send your key as Authorization: Bearer <key>. To set a default Hand, also send X-Ownhand-Hand: prof_.... Your agent then never has to pass it.
claude mcp add --scope user --transport http ownhand https://ownhand.dev/mcp \
--header "Authorization: Bearer $OWNHAND_API_KEY"{
"mcpServers": {
"ownhand": {
"url": "https://ownhand.dev/mcp",
"headers": {
"Authorization": "Bearer <OWNHAND_API_KEY>"
}
}
}
}[mcp_servers.ownhand]
url = "https://ownhand.dev/mcp"
http_headers = { "Authorization" = "Bearer <OWNHAND_API_KEY>" }{
"mcpServers": {
"ownhand": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://ownhand.dev/mcp", "--header", "Authorization: Bearer <OWNHAND_API_KEY>"]
}
}
}The Connect page in the dashboard fills in your key and Hand.
Agent blurb
This tells your agent when to call the tools and to report what you sent. Paste it into your agent instructions.
## Writing in my voice: use the `ownhand` MCP server
When I ask you to write something "in my voice", draft a message/email/reply/PR description
for me to send, or make AI-sounding text sound like me, use the `ownhand` MCP tools.
Don't rewrite it yourself.
1. Call `write` with the draft and the right `occasion`:
chat_dm, chat_channel, email_internal, email_formal, email_cold, email_warm, proposal_cold, proposal_warm, pr_description, review_comment, docs, status_update.
For a reply, set `is_reply: true` and pass the last messages of the thread verbatim as `context_messages`.
If you can't see the thread, ask me to paste it.
2. Show me the draft exactly as returned. Never send or post anything without my OK.
3. After I send it, call `send_feedback` with the `request_id` and **exactly the text I sent** (verdict
`approved` / `edited` / `rejected`, plus my reason if I gave one). This is how it learns my voice. Always do it.
`user_instructions` is only for things I explicitly ask for ("shorter"). Never add your own style advice.
The tool already knows my style.Occasions
Pick the occasion from where the text will be posted and who will read it. Each occasion sets register, length, greeting, sign-off, and formatting. Leave it out if you can't tell.
| Occasion | Use when |
|---|---|
chat_dm | A direct message in Slack, Teams, or similar |
chat_channel | A post or thread reply in a shared channel |
email_internal | An email to a coworker |
email_formal | A formal email to a senior person, a client, or an official |
email_cold | A first email to someone who doesn't know you |
email_warm | An email to someone you already know |
proposal_cold | A proposal to people who don't know you |
proposal_warm | A proposal to people you already work with |
pr_description | A pull or merge request description |
review_comment | A code review comment |
docs | Documentation, a README, a how-to |
status_update | A status update to a manager or a team |
You can also pass recipient_name, recipient_role, and familiarity (cold, warm, or close) when you know them.
Replies
For a reply, set is_reply: true and pass the last messages of the thread, up to 30, word for word as context_messages. The reply matches how you write in that thread: length, casing, greetings, emoji, and punctuation. Facts that appear only in the thread stay out of the rewrite. The thread is never stored.
[
{"author": "dana", "is_me": false, "text": "did the payments deploy go out?"},
{"author": "me", "is_me": true, "text": "not yet, waiting on ci"},
{"author": "dana", "is_me": false, "text": "ok lmk, finance wants it before 3"}
]Learning from what you send
Your Hand learns from what you send. The agent drafts, you edit and send, and then the agent reports exactly what went out with send_feedback.
| What you did | Call |
|---|---|
| Sent the draft as is | send_feedback(request_id, "approved") |
| Edited it, then sent it | send_feedback(request_id, "edited", final_text="...") |
| Threw it away | send_feedback(request_id, "rejected", was_sent=false, reason="...") |
| Picked one of several variants | add chosen_variant=<index> |
A reason in your own words, such as "too formal", becomes a rule right away. A pattern seen in your edits becomes a rule once it shows up twice. Learning runs in the background, and the Hand version goes up when it finishes. In the dashboard you can pin, reject, or reactivate any rule, and roll back to an earlier version.
HTTP API
For scripts and agents without MCP. The base URL is https://ownhand.dev. Requests and responses are JSON. Every request needs Authorization: Bearer hum_.... Add an Idempotency-Key header to POST requests you might retry. A Hand is called a profile in the API.
curl https://ownhand.dev/v1/humanize \
-H "Authorization: Bearer $OWNHAND_API_KEY" -H "content-type: application/json" \
-d '{"text": "...", "profile_id": "prof_...", "occasion": "chat_dm"}'POST /v1/humanize
Rewrites a draft in your Hand. Charged per token from your credit balance.
| Field | Type | Notes |
|---|---|---|
text | string | Required. The draft. |
profile_id | string | Your Hand. |
occasion | string | One of the occasions above. |
recipient | object | name and role. |
axes | object | Adjusts the occasion: channel, familiarity, power, audience, stakes. |
context | object | platform and messages, for replies. Never stored. |
user_instructions | string | Only what the user asked for, such as "shorter". |
options | object | strength (light, medium, strong), length (keep, shorter, longer), variants (1 to 3). |
Response:
{
"id": "req_...",
"text": "payments deploy done at 2:40pm. ping me if finance has questions",
"variants": [],
"violations": [],
"checks": { "facts_kept": true, "length_ratio": 0.31 },
"applied": { "profile_id": "prof_...", "profile_version": 14, "occasion": "chat_dm", "learned_rules_used": 4 },
"usage": { "input_tokens": 1234, "output_tokens": 56 },
"model": "..."
}violations lists checks that still failed after revision. Show the draft anyway and mention them.
Money in the API is in micro-dollars: balance_micro: 4210000 is $4.21. Each model call is charged separately, including revision rounds and the learning calls a feedback triggers.
POST /v1/feedback
Reports what was sent. Returns 201.
| Field | Type | Notes |
|---|---|---|
request_id | string | Required. The id from /v1/humanize. |
verdict | string | Required. approved, edited, or rejected. |
final_text | string | Exactly what was sent. Required when edited. |
was_sent | boolean | Defaults to true. |
reason | string | The user's own words. Becomes a rule right away. |
chosen_variant | number | 0 to 2, when the user picked a variant. |
Profiles
| Endpoint | Purpose |
|---|---|
POST /v1/profiles | Create. Body: { name, personalization, history: [{ type: "sample", text, source: "user_written" }] } |
GET /v1/profiles | List with id, name, version, and sample count |
GET /v1/profiles/:id | Full profile |
PATCH /v1/profiles/:id | Update the name, personalization, rules, or style card |
DELETE /v1/profiles/:id | Delete the profile and its history |
POST /v1/profiles/:id/history | Add samples: { items } |
GET /v1/profiles/:id/style | Style card, learned rules, stats, and sample counts |
PATCH /v1/profiles/:id/rules/:rule | Set a rule's status to active, pinned, or rejected |
GET /v1/profiles/:id/versions | Version history |
POST /v1/profiles/:id/rollback | Restore a version: { version } |
GET /v1/occasions | Occasions, with any custom ones |
GET /v1/usage, GET /v1/me | Usage, and your account with balance_micro |
Errors and limits
Errors return { "detail": "..." } with a status code.
401 | The key is missing, invalid, or revoked. |
402 | You are out of credit. Add credit on the billing page. |
404 | The profile or request does not exist on your account. |
422 | The body is invalid or the occasion is unknown. The detail names the field. |
429 | Rate limited. Wait for the number of seconds in Retry-After. |
Each account can make 60 requests a minute and have 20 Hands and 20 active API keys. Prices are on the pricing page.