API
Check how AI-like a text reads and rewrite the passages that give it away, from your own code. The API doesn’t keep the texts you send or save them as documents.
Authentication
Create a key under Settings → API keys and send it as a bearer token. Keys are shown once; revoke one there if it leaks. All requests and responses are JSON. Call the API from your server, not from a browser. Texts aren’t saved as documents, but on a free account (no plan or bought credits) they’re kept to train and improve our models, as described in the privacy policy.
curl https://qualifical.com/api/v1/credits \
-H "Authorization: Bearer qk_…"Passages
Texts are scored and rewritten a passage at a time, because detectors judge short text poorly: their scores become unreliable under about 600 characters. A passage is one paragraph (one line of the text). A paragraph under 600 characters, such as a heading, joins the paragraph after it; the last one joins the paragraph before. A paragraph over 2,500 characters is split at sentence boundaries into similar-sized parts. /rewrite takes passages by their text, so you send only the ones to rewrite.
Check text
POST /api/v1/detect
Scores each passage of the text, then the whole text as the average of its passages weighted by their words. Costs a credit per word of the passages that were scored. Texts need at least 300 characters; results are more reliable above 600.
| Field | Type | Description |
|---|---|---|
| text | string | The text to check. Up to your account’s per-document word limit. |
curl https://qualifical.com/api/v1/detect \
-H "Authorization: Bearer qk_…" \
-H "Content-Type: application/json" \
-d '{"text": "When I first came to Oxford I got lost three times…"}'{
"aiScore": 0.49,
"verdict": "mixed",
"words": 162,
"creditsCharged": 162,
"attackDetected": false,
"passages": [
{ "index": 0, "aiScore": 0.12, "words": 85, "start": 0, "end": 412, "text": "When I first came to Oxford…" },
{ "index": 1, "aiScore": 0.92, "words": 77, "start": 413, "end": 790, "text": "In today's rapidly evolving…" }
]
}| Field | Type | Description |
|---|---|---|
| aiScore | number | null | Probability the text is AI-written, from 0 to 1. Null only if no passage could be scored. |
| verdict | string | null | human below 0.3, mixed from 0.3 to 0.7, ai above 0.7. |
| words | number | Words in the text. |
| creditsCharged | number | Credits spent: one per word of the passages that were scored. |
| attackDetected | boolean | Hidden characters or look-alike letters that can skew detection. |
| passages | array | Each passage with its index, aiScore, words, character offsets in text, and text. A passage the provider failed on has a null aiScore, and its credits are given back. |
Rewrite passages
POST /api/v1/rewrite
Rewrites passages so they read less like AI, usually the ones /detect scored 0.3 or higher. Send their text; nothing else of the text is needed. Each is rewritten paragraph by paragraph: headings and other short lines that don’t end a sentence stay as they are and aren’t charged. Costs seven credits per word sent for rewriting. Prose works best: tables, lists, and other markdown may not survive.
| Field | Type | Description |
|---|---|---|
| passages | string[] | The text of each passage to rewrite, as it appears in your text: from /detect, or any paragraphs. 1 to 50. |
| tone | string | academic (default; essays, papers), professional (work writing, email), casual (posts, notes), or creative (stories, personal essays). |
| strength | string | light, balanced (default), or strong: how far a rewrite may move from the wording. |
curl https://qualifical.com/api/v1/rewrite \
-H "Authorization: Bearer qk_…" \
-H "Content-Type: application/json" \
-d '{
"passages": ["In today's rapidly evolving…"],
"tone": "casual"
}'{
"passages": [
{ "index": 0, "rewritten": "Nobody tells you how much of the first term is admin…" }
],
"creditsCharged": 539
}| Field | Type | Description |
|---|---|---|
| passages | array | One per passage sent, by its index in your list, with the rewritten text to put in its place, or null if it came back unchanged. |
| creditsCharged | number | Credits spent: seven per word sent for rewriting. |
A passage that comes back unchanged still costs its credits, because the model ran on it. Read rewrites before using them: one can shift the emphasis, change a term, or drop a detail.
Credits
GET /api/v1/credits
Your plan, the credits you can spend, and what each action costs per word. Credits come from the free plan’s month (free), a plan’s period (subscription), and packs you bought (purchased), each null when you have none; expiresAt is when the first of them runs out. The ones that run out soonest are spent first. freeToday is the free credits used of today’s cap since 00:00 UTC, or null without free credits.
{
"plan": "free",
"isPaying": true,
"creditsPerWord": { "detect": 1, "rewrite": 7 },
"available": 33480,
"free": { "available": 3480, "total": 5000, "expiresAt": "2026-11-01T00:00:00.000Z" },
"subscription": null,
"purchased": { "available": 30000, "total": 30000, "expiresAt": "2027-10-24T09:12:00.000Z" },
"freeToday": { "used": 1520, "limit": 2500 }
}Errors and limits
Errors share one shape. Branch on code; message is for people.
{ "error": { "code": "INSUFFICIENT_CREDITS", "message": "This needs 1,240 credits, more than you have left." } }| Code | Status | Meaning |
|---|---|---|
| INVALID_INPUT | 400 | The body is missing a field or has a bad value. |
| TEXT_TOO_SHORT | 400 | Fewer than 300 characters to check. |
| UNAUTHORIZED | 401 | Missing, malformed, or revoked key. |
| INSUFFICIENT_CREDITS | 402 | Not enough credits left. Nothing is charged. |
| DOCUMENT_TOO_LONG | 413 | Over your account’s per-document word limit. |
| RATE_LIMITED | 429 | Over 60 requests per minute for this key. |
| FREE_DAILY_LIMIT | 429 | Only free credits are left, and this would pass today’s cap on them for your account. Nothing is charged; retry after 00:00 UTC. |
| FREE_DAILY_LIMIT_ALL | 429 | Only free credits are left, and today’s cap on free credits across all accounts is reached. Nothing is charged; retry after 00:00 UTC. |
| DETECTION_UNAVAILABLE | 503 | The provider failed. Credits for the failed part are given back; retry later. |
| REWRITE_UNAVAILABLE | 503 | Rewriting failed and its credits were given back. |