Skip to content
GhostVerb

Developers

API documentation

Rewrite text programmatically with a small, predictable REST API. Same models, same per-task billing.

Base URL: https://ghostverb.com/api/v1 JSON · UTF-8 · HTTPS only

Introduction

Rewrites are asynchronous: you create a job, then poll it until it finishes. Most jobs complete within a few seconds; the first job after an idle period may take up to a minute while a GPU worker starts.

Step 1

POST /rewrites

Credits are reserved

Step 2

GET /rewrites/{id}

Poll with backoff

Step 3

status: completed

Read job.output

Authentication

Create a key under Dashboard → API (requires a verified email) and send it as a Bearer token. Keys are shown once and stored only as a SHA-256 hash — rotate them any time.

Authorization: Bearer rw_live_••••••••••••••••••••••••

Treat API keys like passwords. Never embed them in client-side code or public repositories.

Quick start

# 1. Submit a rewrite
curl -X POST https://ghostverb.com/api/v1/rewrites \
  -H "Authorization: Bearer $GHOSTVERB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your text here…", "style": "voice", "intensity": "balanced"}'

# 2. Poll until status is completed / failed / cancelled
curl https://ghostverb.com/api/v1/rewrites/JOB_ID \
  -H "Authorization: Bearer $GHOSTVERB_API_KEY"
import os, time, requests

API = "https://ghostverb.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['GHOSTVERB_API_KEY']}"}

job = requests.post(f"{API}/rewrites", headers=HEADERS, json={
    "text": "Your text here…",
    "style": "voice",
    "intensity": "balanced",
}, timeout=30).json()["job"]

delay = 2
while job["status"] not in ("completed", "failed", "cancelled"):
    time.sleep(delay)
    delay = min(delay * 1.5, 10)
    job = requests.get(f"{API}/rewrites/{job['id']}", headers=HEADERS, timeout=30).json()["job"]

print(job["output"] if job["status"] == "completed" else job["error"])
const API = 'https://ghostverb.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.GHOSTVERB_API_KEY}`, 'Content-Type': 'application/json' };

let { job } = await fetch(`${API}/rewrites`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ text: 'Your text here…', style: 'voice', intensity: 'balanced' }),
}).then((r) => r.json());

let delay = 2000;
while (!['completed', 'failed', 'cancelled'].includes(job.status)) {
  await new Promise((r) => setTimeout(r, delay));
  delay = Math.min(delay * 1.5, 10000);
  ({ job } = await fetch(`${API}/rewrites/${job.id}`, { headers }).then((r) => r.json()));
}
console.log(job.status === 'completed' ? job.output : job.error);
$api = 'https://ghostverb.com/api/v1';
$headers = ['Authorization: Bearer ' . getenv('GHOSTVERB_API_KEY'), 'Content-Type: application/json'];

$request = function (string $method, string $url, ?array $body = null) use ($headers): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true]);
    if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    return json_decode(curl_exec($ch), true);
};

$job = $request('POST', "$api/rewrites", ['text' => 'Your text here…', 'style' => 'voice'])['job'];
while (!in_array($job['status'], ['completed', 'failed', 'cancelled'], true)) {
    sleep(3);
    $job = $request('GET', "$api/rewrites/{$job['id']}")['job'];
}
echo $job['output'] ?? $job['error'];

Endpoints

POST /api/v1/rewrites — Create a rewrite

Validates the text, charges credits and queues the job. Returns 202 with the job object.

textstringrequired5–5,000 words
stylestringoptionalvoice · natural · formal · casual · academic · simple · creative (default voice)
intensitystringoptionallight · balanced · heavy (default balanced)
GET /api/v1/rewrites/{id} — Retrieve a rewrite

Returns the job with its latest status. output is set once status is completed.

GET /api/v1/rewrites — List rewrites

Paginated list of your jobs, newest first (without text).

pageintegeroptionalPage number, default 1
per_pageintegeroptional1–100, default 20
POST /api/v1/quote — Get a quote

Counts words and returns the credit cost without charging anything.

textstringrequiredThe text to price
GET /api/v1/me — Account & balance

Your email, current credit balance and pricing parameters.

The job object

{
  "job": {
    "id": "6f1c2a1e-9d4b-4c55-8a3e-2f0b8c1d7e90",
    "status": "completed",
    "style": "voice",
    "intensity": "balanced",
    "word_count": 412,
    "output_word_count": 398,
    "credits_charged": 5,
    "execution_ms": 4210,
    "error": null,
    "created_at": "2026-10-06 09:24:41",
    "completed_at": "2026-10-06 09:24:46",
    "output": "The rewritten text…"
  }
}
status
pending → queued → processing → completed | failed | cancelled
credits_charged
Credits reserved for the job. Refunded automatically when status is failed or cancelled.
output
The rewritten text. Only present when status is completed.
execution_ms
GPU processing time for the job.
error
Human-readable reason when a job fails.

Errors & limits

Errors return a JSON body like {"error": "message", "code": "…"}.

StatusMeaning
401Missing or invalid API key
402Insufficient credits (code insufficient_credits, includes required and balance)
403Email not verified (code email_unverified)
404Job not found or not yours
422Validation error — too short, too long or missing text
429Rate limit exceeded — retry after the Retry-After header
503Rewrite engine temporarily unavailable — credits were refunded

Rate limit: 60 requests per minute per key. Maximum 5,000 words per job.

Best practices

  • Poll with exponential backoff (start at 2s, cap at 10s) instead of tight loops.
  • Use POST /quote to show users the cost before submitting.
  • Split very long documents at paragraph boundaries into multiple jobs.
  • Store API keys in environment variables or a secrets manager, and rotate them if exposed.
  • Handle 402 by prompting the user to top up, and 503 by retrying later — credits are never lost.

Rewrite it. Leave no trace.

Create an account, top up once, and pay only for the words you rewrite.