API & MCP
Build against the Deft server API or connect Deft to an AI client through MCP. Access and billing are managed from Account.
Endpoint
POST /v1/generate
Use /v1/generate for complete JSON compatibility, or POST /v1/generations and poll the returned status URL for durable long-running work. Keys can be created immediately from Account.
curl https://deftwriting.com/v1/generate -H "Authorization: Bearer $DEFT_API_KEY" -H "Content-Type: application/json" -d '{"prompt":"Write a concise launch memo for a new analytics feature."}'const response = await fetch("https://deftwriting.com/v1/generate", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.DEFT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Write a concise launch memo for a new analytics feature.",
}),
});
const result = await response.json();const job = await fetch("https://deftwriting.com/v1/generations", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.DEFT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: "Write a concise launch memo." }),
}).then((response) => response.json());
// status_url is relative. No Supabase access is required.
const statusUrl = new URL(job.status_url, "https://deftwriting.com");
const status = await fetch(statusUrl, {
headers: { Authorization: "Bearer " + process.env.DEFT_API_KEY },
}).then((response) => response.json());
// Follow Retry-After and do not poll more often than every two seconds.Authentication
Send your API key as a bearer token. API keys are separate from website sessions and must stay server-side.
Rate limits
New keys default to 60 generation requests per 60 seconds. Status polling is limited to 30 requests per 60 seconds per key; follow Retry-After.
Billing
Durable-job billing posts after result delivery; synchronous generation settles before responding. Successful generations have a one-cent minimum. A posted negative balance blocks future requests.
Best practices
Ask for complete documents
Deft was trained to produce whole samples of text from the web. Requests for full documents with the complete context and desired structure generally work best. Requesting fragments, isolated chunks, or partial continuations won't work as well as full documents.
Request
Prompt-first body
Send JSON with a non-empty prompt. Optional generationMode accepts simple, legacy prompt_optimizer, or rewrite. In rewrite mode, prompt is the source text and optional rewriteInstructions describes how to transform it, up to an estimated 50,000 tokens.
For best results, keep inputs below an estimated 100,000 tokens. The API can accept longer prompts, but quality may degrade as inputs grow. Prompts above an estimated 150,000 tokens are rejected before preprocessing or billing.
Optional thinkingLevel accepts faster or smarter, and optional optimizeFor accepts polished or human. Smarter and polished are the defaults. Legacy thinking levels human and max remain accepted and behave like Smarter plus human optimization.
Earlier API versions used one thinkingLevel setting for Faster, Smarter, and More human. That one-field form is now legacy, but remains fully backward compatible: faster maps to Faster plus polished, smarter maps to Smarter plus polished, and human or max maps to Smarter plus human. Existing integrations do not need to change.
For generate-mode requests, optional detailMode accepts strict or creative. Strict mode keeps outline details grounded in facts and context from your prompt. Creative mode may invent supporting specifics to flesh out sparse, narrative, opinion, or exploratory requests. Omit detailMode to let Deft choose the better mode for the prompt.
Optional style and styleKind fields apply an exact style description or match an exact reference sample; style supports up to an estimated 1,000 tokens. The endpoint returns a complete JSON response. Streaming, user-facing model selection, temperature controls, source-material controls, and token budgeting controls are not exposed.
{
"prompt": "Write a concise launch memo for a new analytics feature.",
"thinkingLevel": "smarter",
"optimizeFor": "human",
"detailMode": "strict"
}
{
"prompt": "Write a concise launch memo for a new analytics feature.",
"thinkingLevel": "faster",
"optimizeFor": "human"
}
{
"generationMode": "rewrite",
"prompt": "The source text to rewrite goes here.",
"rewriteInstructions": "Make it shorter and less formal."
}Response
Generated text and usage
{
"text": "The generated draft text...",
"usage": {
"input_tokens": 512,
"output_tokens": 1024,
"thinking_tokens": 180
}
}text is the final Deft output.
usage reports only customer-supplied prompt, style, and rewrite-instruction text as input tokens, counted with the Qwen/Qwen3-14B tokenizer. Internal prompt templates and routing input are excluded. Final text uses the same tokenizer; preprocessing completion and unselected DFT candidate output are reported as thinking tokens.
Operational behavior
Errors, retries, and limits
Immediate errors return an error object with a stable code and human-readable message. Durable generation failures appear in the polled job payload with status: failed and an error object. Optional diagnostics can include a reason or generation id. Failed generations are not charged.
| Status | Code | Action |
|---|---|---|
| 400 | invalid_json, invalid_request | Fix the JSON body. |
| 401 | missing_api_key, invalid_api_key | Send a valid bearer API key. |
| 402 | insufficient_balance | Review billing from Account. |
| 403 | api_key_revoked, api_account_disabled | Use an active key/account. |
| 404 | generation_not_found | Check the job id and use a key from the account that created it. |
| 429 | rate_limited | Wait for the configured window; status polling returns Retry-After. |
| 503 | generation_queue_full, rate_limit_unavailable | Follow Retry-After or retry with backoff. |
| 500/502/503/504 | generation_failed or upstream errors | Retry the request when safe. |
Copyeditor API
Copyedit text and choose alternatives
Use your Deft bearer API key to identify your account and load your saved Copyeditor preferences. Copyeditor costs $0.10 per 1,000 submitted input tokens ($100 per million), rounded up to the nearest cent per run. It uses API credits and accepts up to 10,000 words and 10 MiB of UTF-8 text. Credits are reserved when you submit and charged once the edited result is ready. Failed analyses are not charged. Alternative selections are included. Keep the key on your server.
Create an API key and add credits in Account. The key owner is the user identifier; do not send an email or user ID. API credits are separate from website subscriptions and free daily allowances. Input must contain at least 20 characters. Tokens are counted once from the canonical source using the bundled Qwen3 tokenizer, with a one-cent minimum.
For drafting new copy, use /v1/generate with prompt. For a full rewrite, also send generationMode: "rewrite" and optional rewriteInstructions. Those calls use generation pricing. Copyeditor takes sourceText and returns individual edit choices.
Quick start: submit and choose
Run this example on your server with Node.js 18 or later and DEFT_API_KEY set. The request stays open while the document is analyzed and returns the reviewable result. For safe retries, send an Idempotency-Key header with a persisted unique ID per run (1–200 printable ASCII characters, no spaces). Reusing the same key and canonical source returns the finished result without another model call or charge. An in-flight replay returns 409 request_in_progress. Different source text with that key returns 409 idempotency_conflict. Keys are account-scoped and retained for the session’s lifetime. Without a key, resubmitting starts another paid run.
// Node.js 18+. Set DEFT_API_KEY in your server environment.
const baseUrl = "https://deftwriting.com";
const headers = {
Authorization: `Bearer ${process.env.DEFT_API_KEY}`,
"Content-Type": "application/json",
};
async function api(path, body) {
const response = await fetch(new URL(path, baseUrl), {
method: body === undefined ? "GET" : "POST",
headers,
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(JSON.stringify({ status: response.status, body: data }));
}
return data;
}
// Optional: set a persisted unique ID per intended run for safe retries.
// headers["Idempotency-Key"] = process.env.DEFT_COPYEDIT_REQUEST_ID;
// The request returns when request-bound analysis reaches review.
const run = await api("/v1/copyedit", {
sourceText: "The documents is ready for review.",
});
console.log(run.result.text); // All proposed corrections applied by default.
// Optional automation: follow available recommendations; keep uncertain edits.
const selections = run.result.edits.map(choice => ({
editId: choice.editId,
optionId: choice.recommendationAvailable ? choice.recommendedOptionId : "keep",
}));
const selected = await api(`/v1/copyedit/${run.sessionId}/resolve`, { selections });
console.log(JSON.stringify(selected, null, 2)); // Full response and selected draft.Endpoint reference
POST /v1/copyedit
Authorization: Bearer <DEFT_API_KEY>
Content-Type: application/json
{"sourceText":"The documents is ready for review."}
// 200: { sessionId, userId, status: "review", usage, result }
// The request stays open until analysis finishes.
// result contains:
// sourceText, text, offsetEncoding: "utf-16", edits
// Each choice: editId, start, end, original, category,
// consensus, recommendationAvailable, recommendedOptionId,
// defaultOptionId, selectedOptionId, options
// Each option: id, text, rationale
POST /v1/copyedit/<sessionId>/resolve
Authorization: Bearer <DEFT_API_KEY>
Content-Type: application/json
{"selections":[{"editId":"<returned-edit-id>","optionId":"keep"}]}The returned text applies every proposed correction, including proposals recommended for rejection. Conflicts use the selected replacement, or option A if none was selected. Choices include keep, apply, and both A/B alternatives when present. Offsets address the returned canonical sourceText in UTF-16, with an exclusive end. Resolve accepts returned option IDs and uses defaults for omitted edits. It makes no model calls, saves no completion, and does not train preferences.
POST /v1/copyedit/<sessionId>/complete saves the final document and learns preferences through the same workflow as the website. Send decisions, finalText, and a body idempotencyKey (8–200 characters). Each decision contains editId and decision: accept | reject. Every edit marked reviewable: true needs a decision. Untouched automatic edits do not teach preferences.
POST /v1/copyedit/<sessionId>/complete
Authorization: Bearer <DEFT_API_KEY>
Content-Type: application/json
{
"decisions": [{ "editId": "<returned-edit-id>", "decision": "accept" }],
"finalText": "The author-reviewed final document.",
"idempotencyKey": "completion-unique-1"
}To get the website’s final preview, send decisions instead of selections to /resolve. The server returns text reconstructed from your decisions and the stored source. Accept uses the selected replacement; reject keeps the original. Manual polish or a different A/B choice goes in finalText when completing.
result.reviewBatches lists repeated-rule groups and their edit IDs. Include a group’s ID as decisionBatchId only when making an explicit bulk decision. Individual decisions omit it. Completion returns status: completed, the original usage, and completion containing final/resolved text, saved outcomes and learned preferences.
Retry completion with the exact same body and key to reuse the saved result without repeating learning. A changed completed request returns 409. Learning failure still saves the document and sets completion.learned.unavailable. Completion adds no token charge; resolve and complete share the 60-request-per-minute limit.
Each choice includes recommendedOptionId, defaultOptionId,selectedOptionId, and recommendationAvailable, plus each option’s replacement text and rationale. The example follows recommendations when available and keeps the original text otherwise. Send an empty selections array to restore the all-applied defaults. Resolve returns every option again so you can try another policy without paying for another analysis.
Usage contains only input_tokens and rate_usd_per_1000_tokens(0.1 USD). Input tokens count the canonical source actually charged, once per run; the count is zero for failed analyses and website sessions. Resolve repeats the original charge without charging again. No output or thinking tokens, credit amounts, totals, or internal billing state appear in usage. A 402 insufficient_credits response means the available balance cannot cover the quote. Unknown or duplicate edit IDs and invalid option IDs return 400; resolving before review returns 409. Another owner’s session returns 404. API errors contain error.code anderror.message.
Response: edited text and all edit options
This illustrative response excerpt shows result.edits, the complete list of proposed edits, including automatically applied edits and recommendations to reject. An unchanged document returns an empty list. Each edit includes every available option’s ID, text, and rationale. Conflicts include keep, A, and B. Actual edit IDs and token counts vary. Resolve retains the full edits list.
{
"sessionId": "11111111-1111-4111-8111-111111111111",
"status": "review",
"usage": {
"input_tokens": 7,
"rate_usd_per_1000_tokens": 0.1
},
"result": {
"sourceText": "The documents is ready for review.",
"text": "The documents are ready for review.",
"offsetEncoding": "utf-16",
"edits": [
{
"editId": "edit-1",
"start": 14,
"end": 16,
"original": "is",
"category": "agreement",
"consensus": true,
"recommendationAvailable": true,
"recommendedOptionId": "apply",
"defaultOptionId": "apply",
"selectedOptionId": "apply",
"options": [
{
"id": "keep",
"text": "is",
"rationale": "Keep the original text."
},
{
"id": "apply",
"text": "are",
"rationale": "Use a plural verb with documents."
}
]
}
]
}
}Creation uses your key’s request limit; resolve permits 60 requests per minute per key. Honor Retry-After on 429 responses. Result and billing are read in one database snapshot. After a successful result is durable, auto-recharge uses your existing account settings and cannot fail or repeat the edit. A disconnected analysis is not resumable; replay it with the same idempotency key, or submit a new paid request if the original request had no key.
Model Context Protocol
Connect Deft to your AI client
Use your normal Deft API key for a persistent connection, or sign in to your Deft account through browser OAuth. No MCP-specific key is required.
Server URL
https://deftwriting.com/mcpChoose HTTP or Streamable HTTP if your client asks. API-key authentication is recommended when you want a stable connection without browser login. Account or subscription sign-in uses OAuth and may require logging in again across sessions or workspaces.
- 01Add the server URL, then choose API-key authentication or Deft account sign-in.
- 02For an API key, set DEFT_API_KEY in the client environment. For account sign-in, complete OAuth in the browser.
- 03Return to the client. Restart it or open a new session if the Deft tools do not appear yet.
Codex quickstart
Connect from the command line
Use the normal Deft API key from Account for a persistent connection, or use browser OAuth for Deft account sign-in.
# OAuth
codex mcp add deft --url https://deftwriting.com/mcp
codex mcp login deft
codex mcp list
# API key: ~/.codex/config.toml
[mcp_servers.deft]
url = "https://deftwriting.com/mcp"
bearer_token_env_var = "DEFT_API_KEY"For API-key authentication, make DEFT_API_KEY available to the environment that launches Codex; use the same normal key created under Account, and keep the raw value out of config.toml. For account or subscription sign-in, run the OAuth commands and keep the terminal open through the browser redirect. OAuth may ask you to log in multiple times when Codex does not preserve the grant between sessions or workspaces. Start a new Codex session and use /mcp to confirm that Deft is active.
Authorization
API key or account sign-in
API-key authentication is the persistent option. Deft account sign-in uses OAuth and can require repeated browser login depending on the client.
API key
Use the normal deft_live_… key from Account. Put it in DEFT_API_KEY and point bearer_token_env_var to that name. No special MCP key is needed.
Account sign-in (OAuth)
Sign in through the browser when the client should connect to your Deft account or subscription. You may need to log in multiple times across clients, sessions, workspaces, or machines.
A website subscription does not replace API credits. MCP generations use the API-credit balance and API token pricing with either method. OAuth requests only openid and offline_access; it does not request phone, email, or profile access. API keys are validated on every request and can be revoked from Account.
Tools
Create, check, or cancel a generation
MCP generation is asynchronous. Your client creates a durable job, waits at least two seconds between status checks, and retrieves the result after it succeeds.
| Tool | Input | Purpose |
|---|---|---|
| deft_create_generation | prompt plus optional generation, thinking, optimization, detail, rewrite, and style settings | Starts a job and returns its ID, status, and progress. |
| deft_get_generation | { "id": "job UUID" } | Returns progress, a failure, or the completed text and usage. |
| deft_cancel_generation | { "id": "job UUID" } | Requests cancellation of a queued or running job. |
Create requires prompt and also accepts generationMode, rewriteInstructions, thinkingLevel, optimizeFor, detailMode, style, and styleKind. Status is one of queued, running, succeeded, failed, or cancelled. OAuth create requests and all cancel requests default to 60 requests per 60 seconds; API-key creates use the configured generation limit for that key. Status checks default to 30 requests per 60 seconds. Tool errors tell the client how long to wait after a rate limit.
MCP copyediting
Copyedit and choose alternatives
Use either a Deft API key or OAuth. Copyediting uses your saved author preferences and API-credit balance at $0.10 per 1,000 source input tokens, rounded up to cents. The available balance must cover the reservation.
deft_create_copyedit accepts sourceText (20 or more characters, at most 10,000 words) and an optional idempotencyKey. It keeps the tool call open through request-bound analysis, then returns the result with every proposed correction applied and all alternatives, including keep, source offsets in UTF-16, rationales, and recommended/default/selected option IDs. Reuse the key with the same source for a safe finished replay without another model call or charge.
deft_complete_copyedit accepts sessionId, decisions, finalText and idempotencyKey to save the document and learn preferences. It uses the same completion contract as the REST API and website, with no additional token charge. Exact retries reuse the saved completion.
deft_resolve_copyedit accepts sessionId and selections, an array of { editId, optionId } pairs from the result. For the website review policy, send decisions instead of selections to receive the server-reconstructed preview. With selections, omitted edits retain the default applied option. It returns the full text and edits without additional billing, model calls, saved completion, or preference learning.
usage contains only input_tokens actually charged and rate_usd_per_1000_tokens: 0.1. Resolve repeats the original usage; failed analysis releases its reservation. An in-flight replay returns request_in_progress rather than starting another model run.
OAuth allows 60 creates and 60 resolve requests per minute per account. API-key creation uses the key’s configured limit; resolve allows 60 per minute per key. Refreshes, disconnects, and deployments do not resume editorial work.
Using MCP
Ask for a complete document
Tell your AI client to use Deft, then include the document purpose, audience, facts, desired structure, and tone. For rewriting, provide the source text and transformation instructions; the client can set generationMode to rewrite.
Successful MCP generations use the same token pricing and API-credit balance as the server API. Failed or cancelled jobs are not charged. A posted negative balance blocks new jobs until API credits are added.
Manage API creditsUse Deft to write a complete launch memo for our analytics feature.
Audience: existing customers
Purpose: explain the release and drive adoption
Include: rollout date, eligibility, setup steps, and support contact
Structure: title, short introduction, three sections, and a call to action
Tone: direct and professionalTroubleshooting
If the connection does not finish
- The page stays on Authorizing. Keep the client login command or application open until the browser returns to its callback. Then retry with a fresh login instead of reusing an old authorization URL.
- Authorization succeeds but tools are missing. Restart the client or begin a new session so it reloads the MCP tool list.
- The API key is rejected. Confirm
DEFT_API_KEYis available to the process that launches the client and that the key is active under Account. Restart the client after changing its environment. - Generation is rejected. Check the API-credit balance in Account. If the error is a rate limit or capacity message, wait for the stated retry interval.
Terminology
How Deft measures usage
- Website generation
- One completed draft created in Deft's website or console. It appears in generation history and counts toward website limits.
- API token
- A metering unit reported for API input, output, and thinking work. Tokens are not API keys and are not a spendable balance.
- API credit
- Prepaid balance used only for server-to-server API charges. API credits do not increase website generation capacity.
- Subscription capacity
- The monthly allowance of website generations included in a paid plan. It resets with the billing period and is separate from API credits.
API billing
Token-based API pricing
Generation and rewrite API calls are billed at $2.50 per million customer-supplied input tokens and $12 per million final output and thinking tokens, rounded up to the nearest cent with a one-cent minimum for successful generations. Failed generations are not charged.
Copyeditor costs $0.10 per 1,000 submitted tokens ($100 per million), rounded up to cents. Output and alternative selections are included.
Input billing covers only the prompt, style, and rewrite instructions you send; Deft's internal prompt templates and routing input are excluded. Preprocessing completion, final DFT output, and unselected candidate output remain billable as output or thinking tokens. Other internal evaluation and post-processing model tokens are excluded.
Durable-job charges and auto-recharge run after result delivery. Synchronous POST /v1/generate settles before responding. A charge may take the balance negative; future requests are blocked once that negative balance is posted.
Rate
$2.50 per 1M input tokens; $12 per 1M output and thinking tokens
Rounding
Usage rounds up to the nearest cent, with a one-cent minimum.
Failures
Failed generations are not charged.