Stealthwriter API Documentation
Stealthwriter API Documentation
Introduction
The Stealth Writer API lets you humanize AI-generated text and detect AI-written content from your own applications. Every endpoint accepts a JSON request body and returns a JSON response. All requests are made over HTTPS.
Authentication
Authenticate every request with your API token, sent as a Bearer token in the
Authorization header. The token is tied to your
subscription and looks like sk_live_…. Requests with a
missing, invalid, or disabled token — or a token that is not attached to an active API
subscription — are rejected with 401 Unauthorized.
Authorization: Bearer sk_live_your_api_token
Content-Type: application/json
sk_live_… token. Keep it
secret — treat it like a password and never expose it in client-side code.
Plan limits & quotas
Your API plan can define several usage limits, each checked on every request. When a limit is
reached you receive a 429 Too Many Requests with
code: "limit_exceeded"; the reason
field tells you exactly which limit was hit so you can handle it programmatically.
| Limit | Applies to | reason | Description |
|---|---|---|---|
| Words per request | All endpoints | words_per_input | Maximum number of words allowed in a single request's text. Humanize and the detectors each have their own configured value. |
| Humanizations per day | /humanize | humanizations_per_day | Maximum successful humanize calls per calendar day. |
| AI scans per day | /detector/passage + /detector/sentence | ai_scans_per_day | Maximum successful detector calls per calendar day, shared across both detector endpoints. |
| Words per month | All endpoints | words_per_month | Maximum words consumed per calendar month. Humanize keeps its own monthly pool; the two detector endpoints share one. |
Daily counters reset at midnight and the monthly word total resets on the first of each month
(server time). All counters only include successful (200 OK)
requests — calls that fail validation, hit a limit, or error out do not consume your quota.
429 with
code: "limit_exceeded". Contact support to
adjust your plan.
/api/v1/humanize
Rewrites AI-generated text so it reads as natural, human-written content. Returns one or more humanized variations of the supplied text.
Limits: subject to words_per_input,
humanizations_per_day and
words_per_month. Exceeding any configured limit returns
429 limit_exceeded; limits your plan doesn't configure are
not enforced. See Plan limits and Errors.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | Required | The text to humanize. |
n | integer | Required | Number of humanized variations to generate. |
level | integer | Optional | Humanization intensity. Higher values rewrite more aggressively. |
style | string | Optional | Writing style / tone to apply to the output. |
Request
curl -X POST https://stealthwriter.io/api/v1/humanize \
-H "Authorization: Bearer sk_live_your_api_token" \
-H "Content-Type: application/json" \
-d '{
"text": "The mitochondria is the powerhouse of the cell.",
"n": 1,
"level": 2,
"style": "casual"
}'
const res = await fetch("https://stealthwriter.io/api/v1/humanize", {
method: "POST",
headers: {
"Authorization": "Bearer sk_live_your_api_token",
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "The mitochondria is the powerhouse of the cell.",
n: 1,
level: 2,
style: "casual",
}),
});
const data = await res.json();
$client = new \GuzzleHttp\Client();
$response = $client->post('https://stealthwriter.io/api/v1/humanize', [
'headers' => [
'Authorization' => 'Bearer sk_live_your_api_token',
'Content-Type' => 'application/json',
],
'json' => [
'text' => 'The mitochondria is the powerhouse of the cell.',
'n' => 1,
'level' => 2,
'style' => 'casual',
],
]);
$data = json_decode((string) $response->getBody(), true);
Response 200 OK
{
"backend": "local",
"sentences": [
{
"index": 0,
"input_sentence": "The mitochondria is the powerhouse of the cell.",
"outputs": [
{
"text": "Honestly, the mitochondria is what keeps the cell powered up.",
"readability": 45.29,
"label": 0
}
]
}
]
}
Response fields are passed through from the Stealth Writer engine and may include additional metadata.
/api/v1/detector/passage
Analyzes a passage as a whole and returns the likelihood that it was written by AI.
Limits: subject to words_per_input,
words_per_month and the shared
ai_scans_per_day quota (counts toward the same daily total as
/detector/sentence). Exceeding any configured limit returns
429 limit_exceeded; limits your plan doesn't configure are
not enforced. See Plan limits and Errors.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | Required | The passage to analyze. |
detector | string | Optional | Detection sensitivity. One of easy, normal, or strict. Defaults to normal when omitted. |
Request
curl -X POST https://stealthwriter.io/api/v1/detector/passage \
-H "Authorization: Bearer sk_live_your_api_token" \
-H "Content-Type: application/json" \
-d '{
"text": "Paste the passage you want to analyze here.",
"detector": "normal"
}'
const res = await fetch("https://stealthwriter.io/api/v1/detector/passage", {
method: "POST",
headers: {
"Authorization": "Bearer sk_live_your_api_token",
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Paste the passage you want to analyze here.",
detector: "normal",
}),
});
const data = await res.json();
$client = new \GuzzleHttp\Client();
$response = $client->post('https://stealthwriter.io/api/v1/detector/passage', [
'headers' => [
'Authorization' => 'Bearer sk_live_your_api_token',
'Content-Type' => 'application/json',
],
'json' => [
'text' => 'Paste the passage you want to analyze here.',
'detector' => 'normal',
],
]);
$data = json_decode((string) $response->getBody(), true);
Response 200 OK
{
"ai_prob": 0.9881510734558105,
"human_prob": 0.0118489945307374,
"label": "AI"
}
ai_prob and human_prob are floats between 0 and 1 (not percentages) and sum to 1. label is the engine's verdict for the passage as a whole.
/api/v1/detector/sentence
Breaks the text into sentences and returns a per-sentence AI/human probability and label — useful for highlighting exactly which parts read as AI-generated.
Limits: subject to words_per_input,
words_per_month and the shared
ai_scans_per_day quota (counts toward the same daily total as
/detector/passage). Exceeding any configured limit returns
429 limit_exceeded; limits your plan doesn't configure are
not enforced. See Plan limits and Errors.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | Required | The text to analyze sentence by sentence. |
detector | string | Optional | Detection sensitivity. One of easy, normal, or strict. |
Request
curl -X POST https://stealthwriter.io/api/v1/detector/sentence \
-H "Authorization: Bearer sk_live_your_api_token" \
-H "Content-Type: application/json" \
-d '{
"text": "First sentence. Second sentence to check.",
"detector": "normal"
}'
const res = await fetch("https://stealthwriter.io/api/v1/detector/sentence", {
method: "POST",
headers: {
"Authorization": "Bearer sk_live_your_api_token",
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "First sentence. Second sentence to check.",
detector: "normal",
}),
});
const data = await res.json();
$client = new \GuzzleHttp\Client();
$response = $client->post('https://stealthwriter.io/api/v1/detector/sentence', [
'headers' => [
'Authorization' => 'Bearer sk_live_your_api_token',
'Content-Type' => 'application/json',
],
'json' => [
'text' => 'First sentence. Second sentence to check.',
'detector' => 'normal',
],
]);
$data = json_decode((string) $response->getBody(), true);
Response 200 OK
[
{ "label": "Human" },
{ "label": "AI" }
]
The response is a bare JSON array of per-sentence verdicts, in the order the sentences appear in text. Each item carries only a label; the sentence text itself is not echoed back.
Sentences shorter than 5 words are skipped — the engine will not score them. The array is therefore often shorter than your sentence count, and is [] when every sentence is too short (a 200 OK with an empty array is a valid result, not an error). Do not assume item n corresponds to your n-th sentence.
The detector value (easy / normal / strict) controls how aggressively sentences are flagged.
Errors
Errors come back with the matching HTTP status code in one of two JSON shapes. Most errors use a
consistent envelope with success,
code, reason and
message. Body-validation errors
(422) use Laravel's standard
message + errors shape instead.
Standard error envelope
{
"success": false,
"code": "limit_exceeded",
"reason": "words_per_input",
"message": "This request has 5200 words, which exceeds your plan limit of 5000 words per request."
}
Examples
401 Unauthorized — missing, invalid, or disabled token:
{
"success": false,
"code": "unauthorized",
"reason": "invalid-api-token",
"message": "The API token is invalid or has been disabled."
}
429 Too Many Requests — the request would exceed your monthly word allowance:
{
"success": false,
"code": "limit_exceeded",
"reason": "words_per_month",
"message": "This request's 800 words would exceed your monthly limit of 50000 words (you have used 49500 this month)."
}
429 Too Many Requests — a configured plan limit was reached:
{
"success": false,
"code": "limit_exceeded",
"reason": "ai_scans_per_day",
"message": "You have reached your daily limit of 500 AI scans. Try again tomorrow."
}
422 Unprocessable — body validation failed (note the different shape):
{
"message": "The text parameter is required.",
"errors": {
"text": ["The text parameter is required."],
"n": ["The n parameter is required."]
}
}
Status reference
| Status | code | reason | Meaning |
|---|---|---|---|
| 401 | unauthorized | invalid-api-token | Missing, invalid, or disabled token, or a token not attached to an active API subscription. |
| 422 | n/a | see errors | Request body failed validation. Uses the message + errors shape. |
| 429 | limit_exceeded | words_per_input · words_per_month · humanizations_per_day · ai_scans_per_day | A configured plan limit was reached (per-request words, monthly words, or a per-day quota). |
| 429 | upstream_error | 429 | The engine is receiving too many requests — slow down and retry shortly. |
| 502 | upstream_error / invalid_upstream_response | request-failed / non-json | The Stealth Writer engine returned an error or an invalid response. |
| 503 | service_not_configured | missing-api-config | The service is not configured. Contact support. |
| 504 | upstream_timeout | timeout | The engine did not respond in time. Retry the request. |