Penned API
The same humanizer and detector as the app, from your own code. No waitlist: create a key and start.
1. Get a key
Sign in and open API keys. Create a key and copy it; it is shown only once. Your account can make 50 rewrites and 50 checks per day, shared across all your keys and reset at midnight UTC.
2. Rewrite text
Send between 40 and 1,200 words. A rewrite takes 1 to 3 minutes, so it runs as a job: start it, then check on it every few seconds until it is done. Results are kept for 24 hours.
curl https://penned-ai.vercel.app/v1/jobs \
-H "Authorization: Bearer kal_your_key" \
-H "Content-Type: application/json" \
-d '{"text": "Your AI-assisted draft goes here...", "category": "academic"}'
# {"job": "Jx2...", "status": "queued"}
curl https://penned-ai.vercel.app/v1/jobs/Jx2... -H "Authorization: Bearer kal_your_key"category is optional: general, academic, assignment, essay, story, blog, email, business, marketing, legal, letter or report. While the job runs, status is queued or running, with done and total sections. When it is done:
{
"job": "Jx2...",
"status": "done",
"output": "The rewritten text",
"ai_score_before": 0.99,
"ai_score_after": 0.18,
"meaning_kept": 0.91,
"numbers_kept": 1.0,
"words": 140,
"seconds": 98.4,
"review": []
}review lists anything worth a second look, such as a point the rewrite left out. A job counts as one rewrite against your daily quota.
3. Check text
curl https://penned-ai.vercel.app/v1/detect \
-H "Authorization: Bearer kal_your_key" \
-H "Content-Type: application/json" \
-d '{"text": "Any text of 40 words or more..."}'Returns ai_score (0 = reads as human, 1 = reads as machine), a score per detector, and a score per sentence.
Python
import time, requests
H = {"Authorization": "Bearer kal_your_key"}
job = requests.post("https://penned-ai.vercel.app/v1/jobs", headers=H, json={"text": draft}, timeout=60).json()["job"]
while True:
time.sleep(5)
r = requests.get(f"https://penned-ai.vercel.app/v1/jobs/{job}", headers=H, timeout=60).json()
if r["status"] in ("done", "failed"):
break
print(r.get("output") or r.get("error"))JavaScript
const H = { Authorization: "Bearer kal_your_key", "Content-Type": "application/json" };
const { job } = await (await fetch("https://penned-ai.vercel.app/v1/jobs", { method: "POST", headers: H, body: JSON.stringify({ text: draft }) })).json();
let r;
do {
await new Promise((ok) => setTimeout(ok, 5000));
r = await (await fetch(`https://penned-ai.vercel.app/v1/jobs/${job}`, { headers: H })).json();
} while (r.status === "queued" || r.status === "running");
console.log(r.output ?? r.error);Errors
400: the text is shorter than 40 or longer than 1,200 words.401: the key is missing, wrong or revoked.404: no job with that id for this key's account, or it is older than 24 hours.429: the key has used today's quota.503: the rewriting model is busy or starting up; retry after a short wait.
Keep keys on your server. Anyone with a key can use its quota. The same terms of use apply to the API.