Writer & Humanizer Async Runs
curl --request POST \
--url https://www.stealthgpt.ai/api/stealthify/runs \
--header 'Content-Type: <content-type>' \
--header 'api-token: <api-token>' \
--data '
{
"text": "<string>",
"model": "<string>",
"qualityMode": "<string>",
"outputFormat": "<string>",
"webhookUrl": "<string>",
"webhookSecret": "<string>"
}
'import requests
url = "https://www.stealthgpt.ai/api/stealthify/runs"
payload = {
"text": "<string>",
"model": "<string>",
"qualityMode": "<string>",
"outputFormat": "<string>",
"webhookUrl": "<string>",
"webhookSecret": "<string>"
}
headers = {
"api-token": "<api-token>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'api-token': '<api-token>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
text: '<string>',
model: '<string>',
qualityMode: '<string>',
outputFormat: '<string>',
webhookUrl: '<string>',
webhookSecret: '<string>'
})
};
fetch('https://www.stealthgpt.ai/api/stealthify/runs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.stealthgpt.ai/api/stealthify/runs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'text' => '<string>',
'model' => '<string>',
'qualityMode' => '<string>',
'outputFormat' => '<string>',
'webhookUrl' => '<string>',
'webhookSecret' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: <content-type>",
"api-token: <api-token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.stealthgpt.ai/api/stealthify/runs"
payload := strings.NewReader("{\n \"text\": \"<string>\",\n \"model\": \"<string>\",\n \"qualityMode\": \"<string>\",\n \"outputFormat\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"webhookSecret\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("api-token", "<api-token>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.stealthgpt.ai/api/stealthify/runs")
.header("api-token", "<api-token>")
.header("Content-Type", "<content-type>")
.body("{\n \"text\": \"<string>\",\n \"model\": \"<string>\",\n \"qualityMode\": \"<string>\",\n \"outputFormat\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"webhookSecret\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.stealthgpt.ai/api/stealthify/runs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["api-token"] = '<api-token>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"text\": \"<string>\",\n \"model\": \"<string>\",\n \"qualityMode\": \"<string>\",\n \"outputFormat\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"webhookSecret\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"runId": "<string>",
"status": "<string>",
"statusUrl": "<string>",
"result": "<string>",
"howLikelyToBeDetected": 123,
"wordsSpent": 123,
"creditsSpent": 123,
"remainingCredits": 123,
"billingMode": "<string>",
"meteredChargedCredits": 123,
"content-type": "<string>",
"user-agent": "<string>",
"x-stealthgpt-event": "<string>",
"x-stealthgpt-run-id": "<string>",
"x-stealthgpt-timestamp": "<string>",
"x-stealthgpt-signature": "<string>"
}Endpoints
Writer & Humanizer Async Runs
Create and poll asynchronous humanization runs with optional webhook callbacks
POST
/
api
/
stealthify
/
runs
Writer & Humanizer Async Runs
curl --request POST \
--url https://www.stealthgpt.ai/api/stealthify/runs \
--header 'Content-Type: <content-type>' \
--header 'api-token: <api-token>' \
--data '
{
"text": "<string>",
"model": "<string>",
"qualityMode": "<string>",
"outputFormat": "<string>",
"webhookUrl": "<string>",
"webhookSecret": "<string>"
}
'import requests
url = "https://www.stealthgpt.ai/api/stealthify/runs"
payload = {
"text": "<string>",
"model": "<string>",
"qualityMode": "<string>",
"outputFormat": "<string>",
"webhookUrl": "<string>",
"webhookSecret": "<string>"
}
headers = {
"api-token": "<api-token>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'api-token': '<api-token>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
text: '<string>',
model: '<string>',
qualityMode: '<string>',
outputFormat: '<string>',
webhookUrl: '<string>',
webhookSecret: '<string>'
})
};
fetch('https://www.stealthgpt.ai/api/stealthify/runs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://www.stealthgpt.ai/api/stealthify/runs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'text' => '<string>',
'model' => '<string>',
'qualityMode' => '<string>',
'outputFormat' => '<string>',
'webhookUrl' => '<string>',
'webhookSecret' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: <content-type>",
"api-token: <api-token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.stealthgpt.ai/api/stealthify/runs"
payload := strings.NewReader("{\n \"text\": \"<string>\",\n \"model\": \"<string>\",\n \"qualityMode\": \"<string>\",\n \"outputFormat\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"webhookSecret\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("api-token", "<api-token>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.stealthgpt.ai/api/stealthify/runs")
.header("api-token", "<api-token>")
.header("Content-Type", "<content-type>")
.body("{\n \"text\": \"<string>\",\n \"model\": \"<string>\",\n \"qualityMode\": \"<string>\",\n \"outputFormat\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"webhookSecret\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.stealthgpt.ai/api/stealthify/runs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["api-token"] = '<api-token>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"text\": \"<string>\",\n \"model\": \"<string>\",\n \"qualityMode\": \"<string>\",\n \"outputFormat\": \"<string>\",\n \"webhookUrl\": \"<string>\",\n \"webhookSecret\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"runId": "<string>",
"status": "<string>",
"statusUrl": "<string>",
"result": "<string>",
"howLikelyToBeDetected": 123,
"wordsSpent": 123,
"creditsSpent": 123,
"remainingCredits": 123,
"billingMode": "<string>",
"meteredChargedCredits": 123,
"content-type": "<string>",
"user-agent": "<string>",
"x-stealthgpt-event": "<string>",
"x-stealthgpt-run-id": "<string>",
"x-stealthgpt-timestamp": "<string>",
"x-stealthgpt-signature": "<string>"
}The async Writer & Humanizer API lets you humanize existing text without keeping a long HTTP connection open. Use it for Zapier, n8n, agent platforms, workflow engines, or any integration where the synchronous
All JSON fields (
Billing is identical to text runs. Super is $0.05 / 100 words.
Possible
/api/stealthify request may exceed the caller’s timeout.
model is required. You must explicitly pass "super", "standard", or "lite". Requests that omit model return 400 Bad Request. There is no default, please pick one.The existing
POST /api/stealthify endpoint is unchanged and remains supported for current API clients. This async API is an alternative for long-running humanization, not a replacement.Humanization automatically strips invisible AI text watermarks. Claude and Gemini embed a statistical pattern in their word choices, invisible to a reader, but it travels with copy and paste and can survive light editing. The humanizer rewrites at the sentence and structure level rather than swapping synonyms, which is the category of change those marks are not built to survive. Meaning stays intact; Claude, Gemini SynthID, and other invisible text watermarks do not.
Flow
1
Create a run
Send
POST /api/stealthify/runs with text to humanize, or upload a PDF/DOCX file as multipart/form-data. Optionally include webhookUrl, webhookSecret, and idempotency-key.2
Store the run id
The API returns
202 Accepted with runId, status: "queued", and statusUrl.3
Poll or wait for webhook
Call
GET /api/stealthify/runs/{runId} until status is completed, failed, or cancelled. If webhookUrl was provided, StealthGPT also sends the terminal payload to that URL.Authentication
string
required
Your Stealth API token from the Stealth API dashboard.
Create a run
POST https://www.stealthgpt.ai/api/stealthify/runs
Headers
string
required
Your Stealth API token.
string
required
application/json to send text, or multipart/form-data to upload a file.string
Optional key, 1-255 characters. Reuse the same key when retrying the same create request to avoid enqueueing duplicate runs.
Request body
string
required
Existing text to humanize — send only that source text, with no instruction wrappers. This endpoint does not generate a new essay from a prompt.
string
required
Required.
"super" is the flagship humanizer, billed at $0.05 / 100 words. "standard" is the previous-generation full rewrite. Super and Standard structural rewrites also remove Claude and Gemini SynthID text watermarks. "lite" is faster and intended for light AI-smell cleanup. "heavy" is deprecated and routes to standard. Omitting model returns 400. See Models.Put only the source text in
text. Do not wrap it with instructions such as “Humanize the following text”, “Rewrite this to sound more human”, or “Make this undetectable”. Those prefixes confuse the model and hurt output quality. Use qualityMode, model, and outputFormat for control instead.string
default:"quality"
"quality" runs quality checks and repairs. "fast" performs a faster single-pass rewrite.string
default:"text"
"text" returns plain text. "markdown" preserves/restores markdown formatting where supported.string
Optional URL that receives a
POST callback when the run reaches a terminal status.string
Optional signing secret, 16-255 characters. When present, StealthGPT signs webhook callbacks with HMAC-SHA256.
Request body (file upload)
Instead of JSON, you can sendmultipart/form-data to humanize the contents of a document. The text is extracted server-side and the run behaves exactly like a text run: the humanized result is returned as plain text in the result field.
file
required
The document to humanize. Supported types:
.pdf, .docx, .txt, .md. Maximum size: 4MB. Scanned or image-only documents are not supported.model required; qualityMode, outputFormat, webhookUrl, webhookSecret optional) can be sent as additional form fields.
curl
curl --request POST \
--url 'https://www.stealthgpt.ai/api/stealthify/runs' \
--header 'api-token: YOUR_API_TOKEN' \
--form 'file=@essay.pdf' \
--form 'qualityMode=quality' \
--form 'model=super'
standard and lite are $0.20 / 1,000 words.
Response: 202 Accepted
string
required
Identifier of the queued humanization run.
string
required
Always
"queued" for a successful create response.string
required
Relative polling URL:
/api/stealthify/runs/{runId}.{
"runId": "stealthify-api-run:abc123",
"status": "queued",
"statusUrl": "/api/stealthify/runs/stealthify-api-run:abc123"
}
Poll run status
GET https://www.stealthgpt.ai/api/stealthify/runs/{runId}
Headers
string
required
Your Stealth API token. The run must belong to this token’s account.
Path parameters
string
required
The
runId returned from POST /api/stealthify/runs.Queued response
{
"runId": "stealthify-api-run:abc123",
"status": "queued"
}
Running response
{
"runId": "stealthify-api-run:abc123",
"createdAt": "2026-05-28T14:55:00.000Z",
"updatedAt": "2026-05-28T14:55:03.000Z",
"currentOperation": "humanizing",
"status": "running"
}
Completed response
The completed polling response is also the successful webhook callback body.string
required
Humanized output text.
number
required
Human-likeness score from 0 to 100. Higher is better: a higher score means the result reads as more human and is less likely to be flagged as AI; a lower score means it is more likely to be detected as AI-generated. (The field name is historical — treat a higher value as the better result.)
number
required
Words charged for this run.
number
required
Same as
wordsSpent. Kept on the async contract; it is not a separate charge.number
required
Prepaid word balance after this charge.
string
required
"prepaid" when covered from balance, or "payg" when metered usage was applied.number
required
Words added to metered usage for this request (0 when fully prepaid).
{
"runId": "stealthify-api-run:abc123",
"createdAt": "2026-05-28T14:55:00.000Z",
"updatedAt": "2026-05-28T14:56:12.000Z",
"currentOperation": null,
"status": "completed",
"result": "Renewable energy has clear benefits for communities and the climate...",
"howLikelyToBeDetected": 92,
"wordsSpent": 260,
"creditsSpent": 260,
"remainingCredits": 985000,
"billingMode": "prepaid",
"meteredChargedCredits": 0
}
Failed response
{
"runId": "stealthify-api-run:abc123",
"createdAt": "2026-05-28T14:55:00.000Z",
"updatedAt": "2026-05-28T14:56:12.000Z",
"currentOperation": null,
"status": "failed",
"error": {
"code": "humanize_failed",
"message": "Humanization failed"
}
}
error.code values:
| Code | Meaning |
|---|---|
humanize_failed | The humanization pipeline failed before output was available. |
payg_terms_required | The user ran out of prepaid credits and has not accepted pay-as-you-go terms. |
payment_required | Credits or pay-as-you-go access were not available when billing was finalized. |
billing_delayed | Billing was temporarily delayed; create a new run later if needed. |
result_unavailable | The run completed, but its linked humanized result is no longer available. |
cancelled | The run was cancelled before completion. |
Webhook callbacks
IfwebhookUrl is present in the create request, StealthGPT sends a best-effort POST when the run reaches a terminal status: completed, failed, or cancelled.
Headers
string
application/jsonstring
StealthGPT-API-Webhook/1.0string
stealthify.run.completed, stealthify.run.failed, or stealthify.run.cancelled.string
The run id.
string
Unix timestamp in seconds. Present on every webhook callback.
string
Present when
webhookSecret was provided. Format: v1={hex_hmac_sha256}.Delivery behavior
- Webhook delivery uses a 10-second timeout.
- Network errors, timeouts,
429, and5xxresponses are retried up to 3 attempts. 4xxresponses other than429are treated as final failures.- Webhook delivery is best-effort: a failed callback does not change the run status.
- The callback body is the same terminal payload returned by
GET /api/stealthify/runs/{runId}. - When
webhookSecretis provided, the signature is computed over${timestamp}.${rawBody}using HMAC-SHA256. - Reject signed callbacks with old timestamps. A 5-minute tolerance is recommended.
Verify a signature
Use the raw request body exactly as received. Re-serializing parsed JSON can change whitespace and produce a different signature.nodejs
import { createHmac, timingSafeEqual } from 'node:crypto'
const WEBHOOK_TOLERANCE_SECONDS = 5 * 60
function verifyStealthGptWebhook({ rawBody, timestamp, signature, secret }) {
if (!signature) return false
const timestampSeconds = Number(timestamp)
const nowSeconds = Math.floor(Date.now() / 1000)
if (
!Number.isFinite(timestampSeconds) ||
Math.abs(nowSeconds - timestampSeconds) > WEBHOOK_TOLERANCE_SECONDS
) {
return false
}
const [version, receivedDigest] = signature.split('=')
if (version !== 'v1' || !receivedDigest) return false
const expectedDigest = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
const expected = Buffer.from(expectedDigest, 'hex')
const received = Buffer.from(receivedDigest, 'hex')
return (
expected.length === received.length &&
timingSafeEqual(expected, received)
)
}
Examples
curl --request POST \
--url 'https://www.stealthgpt.ai/api/stealthify/runs' \
--header 'Content-Type: application/json' \
--header 'api-token: YOUR_API_TOKEN' \
--header 'idempotency-key: humanize-job-123' \
--data '{
"text": "Renewable energy offers several significant advantages in our modern world...",
"qualityMode": "quality",
"model": "super",
"outputFormat": "text",
"webhookUrl": "https://example.com/webhooks/stealthify",
"webhookSecret": "whsec_your_random_signing_secret"
}'
import time
import requests
headers = {
"api-token": "YOUR_API_TOKEN",
"Content-Type": "application/json",
"idempotency-key": "humanize-job-123",
}
create_response = requests.post(
"https://www.stealthgpt.ai/api/stealthify/runs",
headers=headers,
json={
"text": "Renewable energy offers several significant advantages in our modern world...",
"qualityMode": "quality",
"model": "super",
"outputFormat": "text",
},
timeout=30,
)
create_response.raise_for_status()
run = create_response.json()
status_url = f"https://www.stealthgpt.ai{run['statusUrl']}"
while True:
status_response = requests.get(
status_url,
headers={"api-token": "YOUR_API_TOKEN"},
timeout=30,
)
status_response.raise_for_status()
status = status_response.json()
if status["status"] in ("completed", "failed", "cancelled"):
print(status)
break
time.sleep(10)
const createResponse = await fetch('https://www.stealthgpt.ai/api/stealthify/runs', {
method: 'POST',
headers: {
'api-token': process.env.STEALTHGPT_API_TOKEN,
'Content-Type': 'application/json',
'idempotency-key': 'humanize-job-123',
},
body: JSON.stringify({
text: 'Renewable energy offers several significant advantages in our modern world...',
qualityMode: 'quality',
model: 'super',
outputFormat: 'text',
}),
signal: AbortSignal.timeout(30_000),
})
if (!createResponse.ok) {
throw new Error(`create failed: ${createResponse.status}`)
}
const run = await createResponse.json()
for (;;) {
const statusResponse = await fetch(`https://www.stealthgpt.ai${run.statusUrl}`, {
headers: { 'api-token': process.env.STEALTHGPT_API_TOKEN },
signal: AbortSignal.timeout(30_000),
})
if (!statusResponse.ok) {
throw new Error(`poll failed: ${statusResponse.status}`)
}
const status = await statusResponse.json()
if (['completed', 'failed', 'cancelled'].includes(status.status)) {
console.log(status)
break
}
await new Promise((resolve) => setTimeout(resolve, 10_000))
}
Errors
| Endpoint | Status | Meaning |
|---|---|---|
POST /api/stealthify/runs | 400 | Invalid request body, idempotency-key, or uploaded file (unsupported type, over 4MB, empty, or no readable text). |
POST /api/stealthify/runs | 401 | Missing api-token header or token not found. |
POST /api/stealthify/runs | 402 | Insufficient credits, pay-as-you-go not available, or payment method missing. |
POST /api/stealthify/runs | 500 | Server error while creating the run. |
GET /api/stealthify/runs/{runId} | 400 | Invalid runId. |
GET /api/stealthify/runs/{runId} | 401 | Missing api-token header or token not found. |
GET /api/stealthify/runs/{runId} | 404 | Run not found or belongs to another account. |
GET /api/stealthify/runs/{runId} | 500 | Server error while reading status. |
Usage notes
- Use
idempotency-keywhenever your create request can be retried by a queue, workflow engine, or HTTP client. - Poll every 5-15 seconds; there is no benefit to polling every second.
- Store
runId,statusUrl, and your own job id together so webhook handlers can reconcile callbacks with queued work. - Send only the source text in
text— no “humanize / rewrite / make undetectable” instruction wrappers. - This endpoint only humanizes existing text. To generate a new essay or article from a prompt, keep using
POST /api/stealthifyorPOST /api/stealthify/agent, depending on the workflow.