Owner: Engineering Team | Last Updated: 2026-01-30 | Status: Current
The detector API analyzes text against 9 external AI detection services and returns per-detector scores plus an aggregate. Two endpoints exist: /api/feature/detector/ (primary) and /api/feature/ai-detector/ (alternative). Both require JWT authentication.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/feature/detector/ |
Yes | Primary detection endpoint. Runs text against all 9 external |
| POST | /api/feature/ai-detector/ |
No | Alternative detection endpoint. The frontend code references |
Primary detection endpoint. Runs text against all 9 external detectors.
Authorization: Bearer <jwt_access_token>
Content-Type: application/json
{
"text": "The text to analyze for AI content...",
"content": "The text to analyze for AI content...",
"output_text": "",
"is_input": true,
"history_id": "abc123"
}
| Field | Type | Required | Description |
|---|---|---|---|
text |
string | Yes | The text to analyze |
content |
string | Yes | Same value as text (both fields are required by the backend) |
output_text |
string | Yes | Humanized output text, or empty string if scanning original input |
is_input |
boolean | Yes | true when scanning original input, indicates this is the source text |
history_id |
string|null | No | Document history ID for linking detection results to a document |
{
"result": "ai_generated",
"items": [
{ "name": "GPTzero", "score": 0.85, "is_human": false },
{ "name": "ZeroGPT", "score": 0.72, "is_human": false },
{ "name": "Sapling", "score": 0.65, "is_human": false },
{ "name": "Copyleaks", "score": 0.90, "is_human": false },
{ "name": "Writer", "score": 0.55, "is_human": false },
{ "name": "Turnitin", "score": 0.78, "is_human": false },
{ "name": "Originality", "score": 0.82, "is_human": false },
{ "name": "Crossplag", "score": 0.60, "is_human": false },
{ "name": "Content at Scale", "score": 0.45, "is_human": true }
],
"ai_score": 0.70,
"credit": 4850
}
| Field | Type | Description |
|---|---|---|
result |
string | Classification result (e.g., "ai_generated", "human_written") |
items |
array | Per-detector results with name, score, and human/AI classification |
ai_score |
number | Aggregate AI probability score (0-1 decimal) |
credit |
number | User's updated credit balance after deduction |
Scores are 0-1 decimal values, NOT 0-100 percentages. The frontend converts for display:
// Frontend display logic
const displayScore = (score * 100).toFixed(0) + '%'
The corresponding TypeScript interface for per-detector scores stored on the frontend:
interface StatusProps {
is_scored: string | boolean | null
total: string | boolean | null
gptZero: string | boolean | null
copyLeaks: string | boolean | null
zeroGPT: string | boolean | null
crossPlag: string | boolean | null
sapling: string | boolean | null
writer: string | boolean | null
contScale: string | boolean | null
originality: string | boolean | null
turnitin: string | boolean | null
}
Score field values:
false: Detector did not return a result for this requestnull: Detector not queried or unavailable| Status | Body detail |
Description |
|---|---|---|
| 401 | "Authentication credentials were not provided" |
Missing or invalid JWT |
| 403 | "Your subscription is paused" |
User's subscription is paused |
| 403 | "word_limit" |
Text exceeds the user's per-request word limit |
| 403 | "Your content exceeds the limit of your plan" |
Text exceeds plan's per-request word cap |
| 403 | "You have not enough credits" |
Insufficient word credits for this request |
| 429 | — | Rate limited |
| 500 | — | Server error / detector service failure |
Error response body format:
{
"detail": "Your content exceeds the limit of your plan",
"message": "...",
"error": "..."
}
The frontend checks error fields in this order: detail, message, error.
Alternative detection endpoint. The frontend code references this endpoint but the primary Detector component uses /api/feature/detector/. The exact behavioral difference requires backend documentation (see ML engineer questions Q55).
Same format as /api/feature/detector/.
Same format with potential extended metadata per detector.
| Constraint | Value | Source |
|---|---|---|
| Minimum text length | 50 words | Frontend validation (DetectorButton.tsx) |
| Maximum per-request | Varies by plan (plan_per_request_limit) |
Backend enforcement + frontend pre-check |
| Credit deduction | Based on word count | Deducted from user.credit pool |
| Character limit | 150,000 characters | Frontend validation (Humanizer.tsx) |
Detection consumes credits from the user's main credit pool (user.credit). The updated balance is returned in the response credit field and immediately synced to the frontend session.
Credit balance = session.user.credit + session.user.referral.credits
See Key Concepts for plan-specific credit limits.
import { detector } from '@/app/api/auth/register'
// Build payload
const userData = {
history_id: historyId,
text: sanitizedText,
content: sanitizedText,
output_text: humanizedResult || '',
is_input: true,
}
// Call API
const response = await detector({ data: userData, token: session.access_token })
// Use response
const classification = response.data.result // "ai_generated" | "human_written"
const perDetectorItems = response.data.items // Array of detector results
const aggregateScore = response.data.ai_score // 0-1 decimal
const updatedCredits = response.data.credit // New credit balance
| Date | Author | Change |
|---|---|---|
| 2026-01-30 | Admin | Initial creation |
| 2026-01-30 | Docs team | Rewritten with real request/response formats, StatusProps interface, error codes, credit system, validation rules |
Prev: API Reference - Humanizer Endpoints | Next: API - Document Endpoints | Up: WalterWrites