Docs

Ownhand is the voice layer for AI agents. They write in your voice, fitted to the occasion, with every fact kept.

Quickstart

  1. Sign in to the dashboard and create an API key. It starts with hum_ and is shown once.
  2. Create your Hand: paste 5 to 10 things you wrote yourself, such as Slack messages, emails, and PR descriptions.
  3. Add the MCP server to your client with one of the configs below.
  4. 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/mcp

Connect 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.

Terminal
claude mcp add --scope user --transport http ownhand 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.

OccasionUse when
chat_dmA direct message in Slack, Teams, or similar
chat_channelA post or thread reply in a shared channel
email_internalAn email to a coworker
email_formalA formal email to a senior person, a client, or an official
email_coldA first email to someone who doesn't know you
email_warmAn email to someone you already know
proposal_coldA proposal to people who don't know you
proposal_warmA proposal to people you already work with
pr_descriptionA pull or merge request description
review_commentA code review comment
docsDocumentation, a README, a how-to
status_updateA 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 didCall
Sent the draft as issend_feedback(request_id, "approved")
Edited it, then sent itsend_feedback(request_id, "edited", final_text="...")
Threw it awaysend_feedback(request_id, "rejected", was_sent=false, reason="...")
Picked one of several variantsadd 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.

FieldTypeNotes
textstringRequired. The draft.
profile_idstringYour Hand.
occasionstringOne of the occasions above.
recipientobjectname and role.
axesobjectAdjusts the occasion: channel, familiarity, power, audience, stakes.
contextobjectplatform and messages, for replies. Never stored.
user_instructionsstringOnly what the user asked for, such as "shorter".
optionsobjectstrength (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.

FieldTypeNotes
request_idstringRequired. The id from /v1/humanize.
verdictstringRequired. approved, edited, or rejected.
final_textstringExactly what was sent. Required when edited.
was_sentbooleanDefaults to true.
reasonstringThe user's own words. Becomes a rule right away.
chosen_variantnumber0 to 2, when the user picked a variant.

Profiles

EndpointPurpose
POST /v1/profilesCreate. Body: { name, personalization, history: [{ type: "sample", text, source: "user_written" }] }
GET /v1/profilesList with id, name, version, and sample count
GET /v1/profiles/:idFull profile
PATCH /v1/profiles/:idUpdate the name, personalization, rules, or style card
DELETE /v1/profiles/:idDelete the profile and its history
POST /v1/profiles/:id/historyAdd samples: { items }
GET /v1/profiles/:id/styleStyle card, learned rules, stats, and sample counts
PATCH /v1/profiles/:id/rules/:ruleSet a rule's status to active, pinned, or rejected
GET /v1/profiles/:id/versionsVersion history
POST /v1/profiles/:id/rollbackRestore a version: { version }
GET /v1/occasionsOccasions, with any custom ones
GET /v1/usage, GET /v1/meUsage, and your account with balance_micro

Errors and limits

Errors return { "detail": "..." } with a status code.

401The key is missing, invalid, or revoked.
402You are out of credit. Add credit on the billing page.
404The profile or request does not exist on your account.
422The body is invalid or the occasion is unknown. The detail names the field.
429Rate 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.