Bulk Async Runs
curl --request POST \
--url https://www.stealthgpt.ai/api/stealthify/runs/bulk \
--header 'Content-Type: <content-type>' \
--header 'api-token: <api-token>' \
--data '
{
"items": [
{}
],
"items[].idempotencyKey": "<string>"
}
'import requests
url = "https://www.stealthgpt.ai/api/stealthify/runs/bulk"
payload = {
"items": [{}],
"items[].idempotencyKey": "<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({items: [{}], 'items[].idempotencyKey': '<string>'})
};
fetch('https://www.stealthgpt.ai/api/stealthify/runs/bulk', 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/bulk",
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([
'items' => [
[
]
],
'items[].idempotencyKey' => '<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/bulk"
payload := strings.NewReader("{\n \"items\": [\n {}\n ],\n \"items[].idempotencyKey\": \"<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/bulk")
.header("api-token", "<api-token>")
.header("Content-Type", "<content-type>")
.body("{\n \"items\": [\n {}\n ],\n \"items[].idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.stealthgpt.ai/api/stealthify/runs/bulk")
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 \"items\": [\n {}\n ],\n \"items[].idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"runs": [
{}
]
}Endpoints
Bulk Async Runs
Queue up to 100 writer or humanizer runs in one request
POST
/
api
/
stealthify
/
runs
/
bulk
Bulk Async Runs
curl --request POST \
--url https://www.stealthgpt.ai/api/stealthify/runs/bulk \
--header 'Content-Type: <content-type>' \
--header 'api-token: <api-token>' \
--data '
{
"items": [
{}
],
"items[].idempotencyKey": "<string>"
}
'import requests
url = "https://www.stealthgpt.ai/api/stealthify/runs/bulk"
payload = {
"items": [{}],
"items[].idempotencyKey": "<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({items: [{}], 'items[].idempotencyKey': '<string>'})
};
fetch('https://www.stealthgpt.ai/api/stealthify/runs/bulk', 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/bulk",
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([
'items' => [
[
]
],
'items[].idempotencyKey' => '<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/bulk"
payload := strings.NewReader("{\n \"items\": [\n {}\n ],\n \"items[].idempotencyKey\": \"<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/bulk")
.header("api-token", "<api-token>")
.header("Content-Type", "<content-type>")
.body("{\n \"items\": [\n {}\n ],\n \"items[].idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.stealthgpt.ai/api/stealthify/runs/bulk")
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 \"items\": [\n {}\n ],\n \"items[].idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"runs": [
{}
]
}Send many texts in one request. Each item becomes its own async run: it takes the same fields, is billed the same way, and has its own
Status, completed and failed responses, and webhook callbacks, are those of a single run.
runId, status URL and webhook.
model is required on every item. Pass "super", "standard", or "lite". An item without model fails the whole request with 400 Bad Request.Flow
1
Send the items
Send
POST /api/stealthify/runs/bulk with up to 100 items. Each item is a POST /api/stealthify/runs JSON body.2
Store the run ids
The API returns
202 Accepted with one runId and statusUrl per item, in the same order as items.3
Collect the results
Set
webhookUrl on the items to get each result as it finishes, or poll each statusUrl with GET /api/stealthify/runs/{runId}.Headers
string
required
Your Stealth API token from the Stealth API dashboard.
string
required
application/json. File uploads aren’t supported here; send a file to POST /api/stealthify/runs instead.Request body
object[]
required
1-100 runs to queue. Each item takes every JSON field of
POST /api/stealthify/runs: text (or prompt), model, rephrase, tone, writingMode, qualityMode, outputFormat, preservation, preservationOpen, preservationClose, preservationMaxLookahead, preservationMode, webhookUrl and webhookSecret, with the same defaults and limits. Items don’t have to match: one can humanize with preservation while the next writes from a prompt.string
Optional key, 1-255 characters, unique within the request. It does what the
idempotency-key header does on a single run: sending the same item with the same key again returns the run it already started, without queueing or billing it twice. Use your own item id. To redo an item whose run failed, send it with a new key.Limits
- 100 items per request. For more, send several requests.
- 4.5 MB per request body. A larger body is rejected with
413before it reaches the API. Long articles may need fewer than 100 items per request. - 3,000 words per writer prompt (
rephrase: false), as on single runs. - One batch at a time. While runs from your previous bulk request are still queued or running, a new bulk request gets
409, unless it sends every one of those items again with the sameidempotencyKeys (a resend, for example after a429or500). Either way an account never has more than 100 bulk runs going at once. A batch still going after an hour stops holding up the next one. Single runs aren’t affected. - The batch is checked against your balance as a whole. If your balance or pay-as-you-go can’t cover the estimate for every item, nothing is queued and you get
402. The check is an estimate (input words plus as many output words): each run is billed when it finishes, on what it actually wrote, so a run can still endfailedwith a billingerror.code, for example a writer run whose article is longer than its prompt.
Response: 202 Accepted
object[]
required
One entry per item, in the same order as
items. Each is the same response a single POST /api/stealthify/runs returns: runId, status (always "queued") and statusUrl.{
"runs": [
{
"runId": "stealthify-api-run:abc123",
"status": "queued",
"statusUrl": "/api/stealthify/runs/stealthify-api-run:abc123"
},
{
"runId": "stealthify-api-run:def456",
"status": "queued",
"statusUrl": "/api/stealthify/runs/stealthify-api-run:def456"
}
]
}
Examples
curl --request POST \
--url 'https://www.stealthgpt.ai/api/stealthify/runs/bulk' \
--header 'Content-Type: application/json' \
--header 'api-token: YOUR_API_TOKEN' \
--data '{
"items": [
{
"idempotencyKey": "post-1041",
"prompt": "Read [[A1]]our pricing guide[[A2]] before you buy.",
"model": "super",
"preservation": true,
"webhookUrl": "https://example.com/webhooks/stealthify"
},
{
"idempotencyKey": "post-1042",
"prompt": "Write a short guide to choosing running shoes",
"rephrase": false,
"model": "super",
"webhookUrl": "https://example.com/webhooks/stealthify"
}
]
}'
import time
import requests
headers = {"api-token": "YOUR_API_TOKEN"}
articles = [
{"id": "post-1041", "html_text": "Read [[A1]]our pricing guide[[A2]] before you buy."},
]
def create_runs(chunk):
while True:
response = requests.post(
"https://www.stealthgpt.ai/api/stealthify/runs/bulk",
headers=headers,
json={
"items": [
{
"idempotencyKey": article["id"],
"prompt": article["html_text"],
"model": "super",
"preservation": True,
}
for article in chunk
]
},
timeout=60,
)
if response.status_code in (409, 429):
time.sleep(int(response.headers.get("Retry-After", "30")))
continue
response.raise_for_status()
return response.json()["runs"]
# One batch at a time: wait for every run of a batch before sending the next
for start in range(0, len(articles), 100):
chunk = articles[start:start + 100]
pending = {run["runId"]: (article, run) for article, run in zip(chunk, create_runs(chunk))}
while pending:
time.sleep(10)
for run_id, (article, run) in list(pending.items()):
status = requests.get(
f"https://www.stealthgpt.ai{run['statusUrl']}",
headers=headers,
timeout=30,
).json()
if status["status"] in ("completed", "failed", "cancelled"):
print(article["id"], status["status"])
del pending[run_id]
const articles = [
{ id: 'post-1041', htmlText: 'Read [[A1]]our pricing guide[[A2]] before you buy.' },
] // up to 100 per request
const response = await fetch('https://www.stealthgpt.ai/api/stealthify/runs/bulk', {
method: 'POST',
headers: {
'api-token': process.env.STEALTHGPT_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: articles.map((article) => ({
idempotencyKey: article.id,
prompt: article.htmlText,
model: 'super',
preservation: true,
webhookUrl: 'https://example.com/webhooks/stealthify',
})),
}),
signal: AbortSignal.timeout(60_000),
})
if (!response.ok) {
throw new Error(`bulk create failed: ${response.status} ${await response.text()}`)
}
const { runs } = await response.json()
// runs[i] is the run for articles[i]
Errors
| Status | Meaning |
|---|---|
| 400 | Invalid body. info lists each problem with its path, such as ["items", 3, "model"]. Nothing is queued. |
| 401 | Missing api-token header or token not found. |
| 402 | Your balance or pay-as-you-go can’t cover the estimate for the whole batch, or no payment method is set up. Nothing is queued. |
| 409 | Your previous batch still has runs queued or running. Send the next batch when it finishes. Nothing is queued. |
| 413 | The request body is over 4.5 MB. Send fewer items per request. |
| 429 | Too many runs are being queued at once. Wait the seconds in the Retry-After header and send the same request again. Items without an idempotencyKey may already have started, and sending them again queues them again. |
| 500 | Server error. Send the same request again: items with an idempotencyKey come back as the runs they already started, while items without one may already have started and are queued again. |
Usage notes
- With a few hundred items, send them a batch at a time: wait for every run of one batch to finish before sending the next. Set
webhookUrlso each result arrives as it finishes instead of polling every run. - Always set
idempotencyKeyon items you might send twice, such as from a queue or a retrying script. Without it, a resent item is a new run, billed again. - Runs from one request wait in the same queue as every other run, so they finish over minutes, not all at once.