Fileverity API docs
Text detections
Classify plain text as AI-written, mixed or human-written with POST /text/detections. Request and response fields, segment offsets, limits and billing.
POST /text/detections runs one text check. It classifies plain text with fvdoc-3-text and returns the AI-written passages. It is synchronous: the result is in the response.
The examples read the text from a file, so save any passage of 100 words or more as essay.txt first. The Quickstart has a command that writes a sample.
curl https://api.fileverity.com/v1/text/detections \ -H "Authorization: Bearer $FILEVERITY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6b9d1c1e-3b0f-4c0a-9a53-2d1c9d6b7e11" \ -d "$(jq -Rs '{text: ., language: "en"}' essay.txt)"import { readFile } from "node:fs/promises";import { randomUUID } from "node:crypto"; const response = await fetch("https://api.fileverity.com/v1/text/detections", { method: "POST", headers: { Authorization: "Bearer " + process.env.FILEVERITY_API_KEY, "Content-Type": "application/json", "Idempotency-Key": randomUUID(), }, body: JSON.stringify({ text: await readFile("essay.txt", "utf8"), language: "en" }),});const detection = await response.json();import osimport uuidimport requests response = requests.post( "https://api.fileverity.com/v1/text/detections", headers={ "Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"], "Idempotency-Key": str(uuid.uuid4()), }, json={"text": open("essay.txt", encoding="utf-8").read(), "language": "en"},)detection = response.json()Request body
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Plain text of at least 100 words and at most 100,000 characters. |
model | string | No | Default fvdoc-3-text. Accepts the model ID or its snapshot ID fvdoc-3-text-2026-11-03. |
language | string or null | No | ISO 639-1 code: en, fr, de, es, zh. Detected automatically when omitted. |
Unknown fields are rejected with invalid_parameter. When the 100-word minimum is checked, Chinese characters count about two to a word.
Response
{ "id": "tdet_8Hk2pQ4vN9sL1xR7cW3mY6zB", "object": "text.detection", "created": 1793664000, "model": "fvdoc-3-text-2026-11-03", "livemode": true, "language": "en", "verdict": "mixed", "score": 0.97, "ai_fraction": 0.41, "segments": [ { "start": 212, "end": 433, "score": 0.97 }, { "start": 579, "end": 790, "score": 0.93 } ], "usage": { "characters": 1412, "free_characters": 0, "billed_units": 2 }}| Field | Type | Description |
|---|---|---|
id | string | Detection ID, prefixed tdet_. |
object | string | Always text.detection. |
created | integer | Unix timestamp. |
model | string | Snapshot that produced the result. |
livemode | boolean | false for test keys. |
language | string | The language used: the one you sent, or the detected one. |
verdict | string | ai: the text reads as AI-written throughout. mixed: AI-written passages within human text. human: no AI-written passage found. |
score | number | From 0 to 1. Model score for the most AI-like passage. |
ai_fraction | number | From 0 to 1. Share of the evaluated text inside AI-written segments. |
segments | array | The AI-written passages. Each has start, end (exclusive) and score. |
usage | object | characters evaluated, free_characters covered by your free start and billed_units actually charged after the free characters. |
Text detection returns a statistical estimate, not proof of authorship. Segment boundaries are approximate. Use it as one input to a human review, and do not use it alone to make decisions that affect a person.
Segment offsets
start and end are offsets into the text you submitted, counted in Unicode code points, not bytes and not UTF-16 units. Segment boundaries are approximate and can include adjacent sentences. JavaScript strings index by UTF-16, so split into code points first:
const points = Array.from(text);for (const { start, end } of detection.segments) { console.log(points.slice(start, end).join(""));}for segment in detection["segments"]: print(text[segment["start"]:segment["end"]])Python strings already index by code point.
Billing
- Text is billed per 1,000 characters at US$0.14, counted in Unicode code points. Each request is rounded up to whole units, with a minimum of one unit.
- Your free-start text characters are used first and reported in
usage.free_characters.usage.billed_unitsis the number of units charged after them, so the cost isbilled_unitstimes US$0.14. Example: 1,412 characters with no free characters left bill 2 units, US$0.28. - Requests rejected before evaluation are not charged: invalid parameters, text that is too short or too long, unsupported languages and similar. A check that fails inside the service (
service_unavailable) is released and not charged. - Insufficient funds return
insufficient_balance(402).
What is stored
The text is evaluated and discarded. It is never stored, so it cannot be retrieved later.
Fileverity keeps the result without the text (verdict, scores, passage offsets and usage) for 7 days, so the Console can show it and usage can be billed and audited. A retried request with the same Idempotency-Key gets the saved result for 24 hours. Text detections are synchronous: they do not appear in GET /analyses and do not send webhook events. Test keys record nothing. See Data retention and security.
Errors
| Code | Status | Cause |
|---|---|---|
text_too_short | 400 | Fewer than 100 words. |
text_too_long | 413 | More than 100,000 characters. Split the text into several requests. |
unsupported_language | 400 | The language is not supported, or could not be detected. |
unsupported_media_type | 415 | The body is not sent as application/json. |
invalid_json | 400 | The body is not a JSON object. |
missing_parameter | 400 | The text field is missing. |
invalid_parameter | 400 | A field has the wrong type, an unknown field was sent, or the Idempotency-Key is invalid. |
model_not_found | 404 | The model is unknown. |
insufficient_balance | 402 | The balance cannot cover the request. |
spend_limit_reached | 402 | The key reached its monthly spend limit. |
idempotency_mismatch, idempotency_in_progress | 409 | The Idempotency-Key was used with a different body, or its first request is still running. |
service_unavailable, analysis_failed | 503, 502 | The detection service is temporarily unavailable or busy. Not charged; retry. service_unavailable carries a retry-after header. |
Send an Idempotency-Key so that retrying after a timeout does not bill twice. See Idempotency.