[Developers]
Get an API keyThe Emulate API
Everything the app does, over HTTP. Send a draft and get it back reading like a person wrote it, or send a prompt and get a finished piece. Same model, same words.
01Overview
Base URL: https://www.tryemulate.ai/v1. Every request and response is JSON. A call runs the same pipeline as the app, takes about as long as the app does (a few minutes for a long piece), and shows up in your Recents when it finishes. Keep the connection open, or use streaming to read the text as it is written.
02Authentication
Make a key on your API keys page and send it as a bearer token on every request. A key is shown once when you make it. Revoke it there at any time.
Authorization: Bearer ak_live_...Keep keys server-side. A key can spend every word on your account.
03Plans and words
The API is included on every plan, the free one too. Calls spend the same words as the app: a humanize call costs the words you send and a write call costs the length you ask for. The words are taken when a run starts and given back if it fails, so a failed run costs nothing.
| Plan | Words a month | Most words in one call |
|---|---|---|
| Free | 300, once | 300 |
| Starter | 50,000 | 1,500 |
| Max | 300,000 | 5,000 |
| Enterprise | 2,000,000 | 10,000 |
04POST/humanize
Rewrite a draft so it reads as human. Send at least 40 words and at most your plan's cap.
| Field | Type | Notes |
|---|---|---|
| text | string | The draft. Paragraphs separated by blank lines are kept as paragraphs. |
| stream | boolean | Optional. true to stream the text as it is written instead of one JSON reply. |
| style | object | Optional. How the piece should read: the app's Style menu, below. |
curl https://www.tryemulate.ai/v1/humanize \
-H "Authorization: Bearer $EMULATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Remote work has fundamentally transformed the modern workplace... (at least 40 words)"}'The reply:
{
"id": "0f5a2c1e-6d0b-4a3e-9d2f-2c7d5a0b1e44",
"tool": "humanize",
"text": "Working from home has changed how most offices run...",
"reads_human": null,
"style": {},
"words": { "charged": 137 },
"elapsed": 96
}| Field | Meaning |
|---|---|
| id | The run. It is also the draft's id in your dashboard. |
| text | The rewritten piece, paragraphs separated by blank lines. |
| reads_human | Always null. Runs are no longer scored against a detector; the field is kept so code that reads it keeps working. |
| style | The style the call ran with, in the names below; {} when you sent none. |
| words | charged: the words this call spent, taken when the run started. A run that fails gives them back, so it costs nothing. |
| elapsed | Seconds the run took. |
05POST/write
Write a piece from a prompt, then run it through the same rewrite. The length you ask for is the charge.
| Field | Type | Notes |
|---|---|---|
| prompt | string | What to write, up to 4,000 characters. Name a format if you want one. |
| words | integer | How long, 50 and up, within your plan's cap. Default 300. |
| stream | boolean | Optional, as above. |
| style | object | Optional, as above. It steers the rewrite; the first draft follows the prompt. |
curl https://www.tryemulate.ai/v1/write \
-H "Authorization: Bearer $EMULATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "A blog post on why small teams ship faster", "words": 400}'The reply has the same shape as a humanize reply, with "tool": "write".
06Style
Both endpoints take an optional style object, the same one the app's Style menu sends. Values are the names the menu shows, in any case. Leave a field out, or send null, and the model decides it. Any other field or value is an invalid_request error before the run starts, so a typo costs nothing.
| Field | Values | In the app |
|---|---|---|
| type | essay, blog post, article, news, marketing, explainer, forum post, review | Kind |
| tone | neutral, warm, confident, opinionated, cautious | Tone |
| formality | casual, relaxed, balanced, polished, formal | Formality |
| sentences | punchy, mixed, flowing | Sentences |
| level | simple, plain, standard, advanced, professional | Reading level |
| perspective | I, we, you, they | Point of view |
| concision | tight, balanced, full | Length |
curl https://www.tryemulate.ai/v1/humanize \
-H "Authorization: Bearer $EMULATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "...", "style": {"type": "blog post", "tone": "warm", "level": "plain", "perspective": "I"}}'Style is a hint, not a guarantee. Each field is a tag the model was trained with, and where a tag and your draft disagree, the draft usually wins: a draft written as "we" tends to stay "we" with perspective set to I. For /write, put the point of view and the form in the prompt as well.
07GET/me
The account behind a key: its plan, the words left, and the most a single call may touch.
{ "plan": "max", "words_left": 241200, "max_words_per_call": 5000 }08Streaming
Pass "stream": true and the reply is a server-sent event stream: an accepted frame with the run id, delta frames carrying the text as it is written, then one result frame with the same body a plain call returns. Join the deltas in order to show the piece as it arrives; the text in result is the finished copy.
If the run fails once it has started, the last frame is an error frame, { "t": "error", "error": { "code", "message" } }, with the same codes as a plain call. Problems found before the run starts, such as a bad key or too few words, still come back as a plain HTTP error. A model that has sat idle takes a few minutes to wake up, and no frames arrive between accepted and the first delta while it does. If it does not wake in time, the stream ends with an unavailable error and nothing is charged.
curl -N https://www.tryemulate.ai/v1/humanize \
-H "Authorization: Bearer $EMULATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "...", "stream": true}'
data: {"t": "accepted", "id": "0f5a2c1e-..."}
data: {"t": "delta", "text": "Working from"}
data: {"t": "delta", "text": " home has changed"}
data: {"t": "delta", "text": " how most offices run..."}
data: {"t": "result", "id": "0f5a2c1e-...", "tool": "humanize", "text": "...", ...}09Errors
Errors come back as { "error": { "code", "message" } } with an HTTP status to match.
| Code | Status | When |
|---|---|---|
| unauthorized | 401 | No key, a malformed key, or a revoked one. |
| plan_required | 403 | The account's plan does not include the API. |
| out_of_words | 402 | Not enough words left for this call. |
| over_cap | 413 | More words than the plan handles in one call. |
| too_short | 400 | Fewer than 40 words sent to /humanize. |
| invalid_request | 400 | A malformed body: not a JSON object, a field the endpoint does not take, no text or prompt, text over 100,000 characters, a prompt over 4,000, words not a whole number of 50 or more, or a style field or value not in the style table. |
| failed | 502 | The run could not finish. Nothing was charged. |
| billing_failed | 502 | The run finished but the charge did not go through. |
| unavailable | 503 | The model could not be reached, usually because it is still waking up. Try again in a minute. Nothing was charged. |
10Examples
JavaScript:
const response = await fetch("https://www.tryemulate.ai/v1/humanize", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EMULATE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text }),
});
const run = await response.json();
if (!response.ok) throw new Error(run.error.message);
console.log(run.text);Python:
import os, requests
r = requests.post(
"https://www.tryemulate.ai/v1/humanize",
headers={"Authorization": f"Bearer {os.environ['EMULATE_API_KEY']}"},
json={"text": text},
timeout=900,
)
run = r.json()
if not r.ok:
raise RuntimeError(run["error"]["message"])
print(run["text"])Questions? Write to [email protected].