API Reference
MyWritingTwin Public API v1 documentation
#Authentication
All API requests require a Bearer token in the Authorization header.
Authorization: Bearer mwt_sk_...To obtain an API key:
- Dashboard — Go to Dashboard → API Keys and create a new key.
- CLI — Run
mwt auth loginto authenticate and generate a key.
#Base URL
All endpoints are relative to:
https://mywritingtwin.com/api/v1#Response Format
Every response follows a consistent envelope format. All responses include a meta object with a unique request ID and the API version.
Success response
{
"data": {
"id": "prof_a1b2c3d4e5f6...",
"name": "My Profile",
"status": "completed",
...
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}Paginated response
{
"data": [ ... ],
"pagination": {
"has_more": true,
"next_cursor": "cur_..."
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}Error response
{
"error": {
"type": "validation_error",
"message": "Name is required.",
"code": "missing_required_field",
"details": {
"field": "name"
}
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}#Endpoints
/api/v1/accountReturns information about the authenticated account, including current plan tier and usage.
curl https://mywritingtwin.com/api/v1/account \
-H "Authorization: Bearer mwt_sk_..."Response
{
"data": {
"id": "user_...",
"email": "you@example.com",
"tier": "pro",
"usage": {
"profiles_created": 12,
"profiles_limit": 50,
"api_calls_used": 347,
"api_calls_limit": 1000,
"api_calls_reset_at": "2026-03-06T00:00:00Z"
},
"created_at": "2026-01-15T09:30:00Z"
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}/api/v1/profilesList all writing profiles for the authenticated user. Results are returned with cursor-based pagination.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | Optional | Number of results per page (1-100, default 20). |
| cursor | string | Optional | Cursor for the next page. Use the next_cursor value from a previous response. |
curl "https://mywritingtwin.com/api/v1/profiles?limit=10" \
-H "Authorization: Bearer mwt_sk_..."Response
{
"data": [
{
"id": "prof_a1b2c3d4e5f67890...",
"name": "Professional emails",
"status": "completed",
"locale": "en",
"sample_count": 5,
"created_at": "2026-02-10T14:22:00Z",
"updated_at": "2026-02-10T14:25:00Z",
"completed_at": "2026-02-10T14:25:00Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}/api/v1/profilesCreate a new writing profile from text samples. The profile is generated asynchronously — poll the profile detail endpoint to check status.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Profile name (1-100 characters). |
| samples | string[] | Required | Array of writing samples (1-10 items, each 100-50,000 characters). |
| locale | string | Optional | Language of the samples. One of: en, ja, fr, es. Default: en. |
curl -X POST https://mywritingtwin.com/api/v1/profiles \
-H "Authorization: Bearer mwt_sk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Blog writing style",
"samples": [
"Your first writing sample text here (min 100 chars)...",
"Your second writing sample text here..."
],
"locale": "en"
}'Response
{
"data": {
"id": "prof_a1b2c3d4e5f67890...",
"name": "Blog writing style",
"status": "generating",
"locale": "en",
"sample_count": 2,
"created_at": "2026-03-05T10:00:00Z",
"updated_at": "2026-03-05T10:00:00Z",
"completed_at": null
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}/api/v1/profiles/:idGet details for a specific writing profile. Use this to poll for generation status.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile ID (e.g. prof_a1b2c3d4...). |
curl https://mywritingtwin.com/api/v1/profiles/prof_a1b2c3d4e5f67890... \
-H "Authorization: Bearer mwt_sk_..."Response
{
"data": {
"id": "prof_a1b2c3d4e5f67890...",
"name": "Blog writing style",
"status": "completed",
"locale": "en",
"sample_count": 5,
"created_at": "2026-02-10T14:22:00Z",
"updated_at": "2026-02-10T14:25:00Z",
"completed_at": "2026-02-10T14:25:00Z"
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}generating (in progress), completed (ready to export), failed (generation failed)./api/v1/profiles/:idPermanently delete a writing profile. This action cannot be undone.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile ID (e.g. prof_a1b2c3d4...). |
curl -X DELETE https://mywritingtwin.com/api/v1/profiles/prof_a1b2c3d4e5f67890... \
-H "Authorization: Bearer mwt_sk_..."Response
{
"data": {
"id": "prof_a1b2c3d4e5f67890...",
"deleted": true
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}/api/v1/profiles/:id/exportExport a completed writing profile in the specified format. The profile must have status "completed" — exporting a generating or failed profile returns a 409 Conflict error.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Profile ID (e.g. prof_a1b2c3d4...). |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| format | string | Required | Export format. One of: system-prompt, md, json, txt, runtime-block. |
| mode | string | Optional | Filter to a single communication mode (e.g. en:email:client). Works with system-prompt and runtime-block formats. |
system-prompt— Ready-to-use system prompt for LLMs (ChatGPT, Claude, etc.). Supports?mode=filtering.md— Full profile in Markdownjson— Writing DNA analysis data (structured JSON)txt— Plain text exportruntime-block— RuntimeBlock v2.0 JSON (structured execution format for AI agents and MCP). Supports?mode=filtering. Returns 409 if not available for older profiles.
curl "https://mywritingtwin.com/api/v1/profiles/prof_a1b2c3d4.../export?format=system-prompt" \
-H "Authorization: Bearer mwt_sk_..."curl "https://mywritingtwin.com/api/v1/profiles/prof_a1b2c3d4.../export?format=runtime-block&mode=en:email:client" \
-H "Authorization: Bearer mwt_sk_..."Response
{
"data": {
"id": "prof_a1b2c3d4e5f67890...",
"format": "system-prompt",
"content": "You are a writing assistant that mimics the following style...",
"content_type": "text/plain"
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}{
"error": {
"type": "conflict_error",
"message": "RuntimeBlock JSON is not available for this profile. It may need to be regenerated.",
"code": "runtime_block_unavailable"
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}/api/v1/analyzeAnalyze writing samples and return style metrics without creating a full profile. Useful for quick analysis or previewing results before profile creation.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| samples | string[] | Required | Array of writing samples (1-10 items, each 100-50,000 characters). |
| locale | string | Optional | Language of the samples. One of: en, ja, fr, es. Default: en. |
curl -X POST https://mywritingtwin.com/api/v1/analyze \
-H "Authorization: Bearer mwt_sk_..." \
-H "Content-Type: application/json" \
-d '{
"samples": [
"Your writing sample text here (min 100 chars)..."
]
}'Response
{
"data": {
"analysis": {
"tone": {
"primary": "professional",
"confidence": 0.87,
"traits": ["authoritative", "concise", "warm"]
},
"formality": {
"level": "semi-formal",
"score": 0.65
},
"vocabulary": {
"complexity": "moderate",
"avg_word_length": 5.2,
"unique_word_ratio": 0.72
},
"sentence_structure": {
"avg_length": 18.4,
"variety": "high",
"dominant_pattern": "compound"
}
}
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}#Errors
The API uses conventional HTTP status codes and returns structured error objects. The type field identifies the error category, and code provides a machine-readable error code for programmatic handling.
| Type | Status | Description |
|---|---|---|
| authentication_error | 401 | Missing or invalid API key. |
| authorization_error | 403 | The API key does not have permission for this action. |
| validation_error | 400 | The request body or parameters are invalid. |
| not_found_error | 404 | The requested resource does not exist. |
| rate_limit_error | 429 | Too many requests. Retry after the period indicated in the response. |
| conflict_error | 409 | The resource is in a state that conflicts with the request (e.g. exporting a profile that is still generating). |
| server_error | 500 | An unexpected error on the server. If this persists, contact support. |
{
"error": {
"type": "authentication_error",
"message": "Invalid or missing API key.",
"code": "invalid_api_key"
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}{
"error": {
"type": "rate_limit_error",
"message": "Rate limit exceeded. Try again in 3600 seconds.",
"code": "rate_limit_exceeded",
"details": {
"retry_after": 3600,
"limit": 1000,
"reset_at": "2026-03-06T00:00:00Z"
}
},
"meta": {
"request_id": "req_abc123def456",
"api_version": "v1"
}
}#Rate Limits
Each API key has daily and hourly rate limits applied per tier. Daily limits use a 24-hour rolling window; hourly limits provide burst protection. Rate limit status is included in every response via standard headers:
| Header | Description |
|---|---|
| RateLimit-Limit | Maximum requests allowed in the daily window. |
| RateLimit-Remaining | Requests remaining in the daily window. |
| RateLimit-Reset | Unix timestamp when the daily window resets. |
| X-RateLimit-Hourly-Limit | Maximum requests allowed per hour (burst protection). |
| X-RateLimit-Hourly-Remaining | Requests remaining in the current hourly window. |
When the limit is exceeded, the API returns a 429 response with a retry_after value in seconds. Implement exponential backoff for best results.
Specific limits vary by plan tier. Check your current usage and limits via the GET /account endpoint or your dashboard.
#MCP Server
MyWritingTwin exposes an MCP (Model Context Protocol) server, allowing AI assistants like Claude Desktop and Claude Code to access your writing profiles directly.
Prerequisites
- A MyWritingTwin account (sign up free)
- An API key from your dashboard
Claude Desktop
Add to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"mywritingtwin": {
"url": "https://www.mywritingtwin.com/api/mcp",
"headers": {
"Authorization": "Bearer mwt_sk_..."
}
}
}
}Claude Code
claude mcp add --transport http mywritingtwin https://www.mywritingtwin.com/api/mcp -h "Authorization: Bearer mwt_sk_..."Claude.ai (web) connectors
Adding the connector inside claude.ai? Use https://www.mywritingtwin.com/api/auth/mcp instead: it signs you in with your MyWritingTwin account (OAuth) so no API key is needed. The plain /api/mcp endpoint never prompts for a login, so claude.ai will not show a Connect step there.
Available Tools
| Tool | Tier | Description |
|---|---|---|
| mwt_list_profiles | All | List your writing profiles |
| mwt_get_profile | All | Retrieve a profile (supports format and mode filtering) |
| mwt_analyze | Free | Quick style analysis from writing samples |
| mwt_create_profile | Paid | Generate a full Writing DNA profile (async, 30-90s) |
| mwt_check_generation_status | All | Poll profile generation progress |
| mwt_create_checkout | All | Mint a Stripe checkout link (starter/pro) — user pays in-browser, no login required |
| mwt_get_account | All | Account info, paid status, and usage — poll to confirm a purchase completed |
Example workflow
In Claude, you can create and load a profile in one conversation:
User: Analyze my writing style from these samples: [paste samples]
→ Claude calls mwt_analyze → returns style preview (free, no purchase)
User: Create a full profile from those samples
→ Claude calls mwt_create_profile
→ unpaid? returns a Stripe checkout_url
→ You open the link and pay (Stripe-hosted page — no login needed)
→ Claude calls mwt_get_account → polls until "paid": true
→ Claude calls mwt_create_profile again → returns profile_id + "generating"
→ Claude calls mwt_check_generation_status → "completed!"
→ Claude calls mwt_get_profile → loads your full Writing DNA as contextPaying from inside the chat
No browser login required. mwt_create_checkout (or an unpaid mwt_create_profile) returns a Stripe-hosted checkout_url. Open it, pay, and the purchase is linked to your account automatically. Poll mwt_get_account until paid is true, then create your profile. (Executive is purchased on the pricing page.)
#Webhooks
Receive real-time notifications when profiles are generated, fail, or are deleted. Register a webhook endpoint to get HTTPS POST requests with signed payloads.
Event Types
| Event | Description |
|---|---|
| profile.generation.completed | A profile finished generating successfully. |
| profile.generation.failed | Profile generation failed. |
| profile.deleted | A profile was deleted. |
| test.ping | Test event sent via the test endpoint. |
Payload Example
{
"event": "profile.generation.completed",
"timestamp": "2026-03-30T12:00:00Z",
"data": {
"profile_id": "prof_a1b2c3d4e5f67890...",
"version": 3,
"mode_count": 7,
"modes": ["en:email:internal_team", "ja:email:customers_clients"],
"generated_at": "2026-03-30T12:00:00Z"
}
}Signature Verification
Every delivery includes X-MWT-Signature and X-MWT-Timestamp headers. Verify the signature to ensure the payload is authentic:
const crypto = require('crypto');
function verifyWebhook(secret, timestamp, body, signature) {
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from('sha256=' + expected),
Buffer.from(signature)
);
}
// In your handler:
const sig = req.headers['x-mwt-signature'];
const ts = req.headers['x-mwt-timestamp'];
const valid = verifyWebhook(WEBHOOK_SECRET, ts, JSON.stringify(req.body), sig);import hmac, hashlib
def verify_webhook(secret: str, timestamp: str, body: str, signature: str) -> bool:
expected = hmac.new(
secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)Retry Behavior
Failed deliveries are retried with exponential backoff: 5s, 30s, 2m, 10m, 30m (5 attempts max). After all attempts fail, the delivery is marked as failed. Check delivery status via GET /api/v1/webhooks/:id.
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/webhooks | Register a webhook endpoint |
| GET | /api/v1/webhooks | List your webhook endpoints |
| DELETE | /api/v1/webhooks/:id | Delete a webhook endpoint |
| GET | /api/v1/webhooks/:id | View delivery log (last 50) |
| POST | /api/v1/webhooks/:id | Send a test.ping event |
curl -X POST https://mywritingtwin.com/api/v1/webhooks \
-H "Authorization: Bearer mwt_sk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://yourapp.com/webhooks/mwt", "event_types": ["profile.generation.completed", "profile.deleted"]}'Zapier / Make Integration
No custom app needed — use MWT webhooks directly with Zapier or Make:
- In Zapier, create a Webhooks by Zapier trigger (Catch Hook). Copy the webhook URL.
- Register it via
POST /api/v1/webhookswith your desired event types. - Add a second step: Webhooks by Zapier > GET to fetch the updated profile via
GET /api/v1/profiles/{profile_id}/export?format=system-prompt - Push the content to downstream tools: Gmail drafts, Slack channels, Notion pages, your CMS, etc.
The same flow works with Make (Integromat) using the HTTP webhook module.
#Versioning
The API version is embedded in the URL path (/api/v1/). You can also send a preferred version via the API-Version header:
API-Version: v1When a breaking change is planned, the API will include a deprecation warning header in responses:
X-MWT-Deprecation-Warning: This endpoint version is deprecated. Migrate to /api/v2/ by 2027-01-01.Older API versions will remain available for at least 12 months after a deprecation notice is issued. Monitor response headers for deprecation warnings and subscribe to our changelog for updates.
Enterprise & Agencies
Need to manage voice profiles for multiple users or integrate MyWritingTwin into your product? We offer custom plans with multi-tenant support, higher rate limits, and dedicated onboarding.
Contact enterprise@mywritingtwin.com