Skip to content

API reference

Send text to one endpoint and get a verdict back as JSON. This page lists the request, the response, three working examples and every error you can get.

Authentication

Every request carries a key in a header: Authorization: Bearer <key>. Create a key from the dashboard after you sign in. API access comes with the Education plan today, and with paid plans once billing starts.

A key is a signed token. Its scope, its expiry date and a random id are written inside the key, and the signature shows nobody changed them. Nothing about the key is stored on the server, which has two consequences. The dashboard shows a key once, and cannot show it again. And you cannot revoke a key yourself: it works until it expires, 90 days after you create it. If a key leaks, stop using it and create a new one. To have a leaked key blocked before it expires, email hello@olive.is; it is added to a block list at the next deploy.

POST /api/v1/detect

Send JSON with a text field. It is required and must be at least 50 words; shorter text is refused before it reaches the detector.

An optional sensitivity field sets how much of the text must read as AI-written before it is flagged. "standard" flags above 6%. It is the default, and a missing or unrecognised value is read as standard. "accusation-safe" flags above 15%, and misses more AI-edited text than the default. "sensitive" flags above 2%, and flagged 2 of 1,928 human-written documents in testing. No setting turns an AI verdict into Human.

An optional mode field takes "verdict" (the default) or "edit", which returns more detail for each sentence.

Read the abstained field first. When it is true, the text was under 50 words, the verdict is "Unknown", and nothing else in the response is a reading. Otherwise verdict is "Human", "Mixed" or "AI". fraction_ai is the share of words in sentences labelled AI; it is not a confidence score. word_count is the number of words the detector counted. sentences is an ordered list of {start, end, label, label_3, p_ai, text}: the sentence as the detector split it, its label, and p_ai, an internal model score for that sentence.

To show highlights, render sentences[].text in order. The start and end numbers point into a normalised copy of your text, so they will not line up if you use them to slice your original string.

The response fields

The response contains these fields and no others: verdict, abstained, fraction_ai, fraction_human, fraction_assisted, confidence, word_count, sensitivity, estimate, version, sentences (each {start, end, label, label_3, p_ai, text}), segments, run and source. fraction_human, fraction_assisted, confidence, estimate, version and label_3 appear only when the detector returns them.

confidence and p_ai are internal model scores. Neither is a measured probability that the text was AI-written, that a person used AI, or that the verdict is right. Do not show them to a student as a probability.

The detection service also computes internal values, such as its decision threshold. Those are not published, because they are the numbers someone would tune rewritten text against. A field reaches you only if it is listed above, so a change inside the service cannot add one.

Every reading carries source "our detector" and run, the name of the detector run that produced it. Store run next to any reading you keep. The live run changes from time to time, and the run name is how you know which one produced a stored result.

curl

curl -X POST https://human.olive.is/api/v1/detect \
  -H "Authorization: Bearer $HUMAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "paste at least 50 words of the text you want checked here", "sensitivity": "standard"}'

Python

import os
import requests

response = requests.post(
    "https://human.olive.is/api/v1/detect",
    headers={"Authorization": f"Bearer {os.environ['HUMAN_API_KEY']}"},
    json={"text": text, "sensitivity": "standard"},
    timeout=120,
)
result = response.json()

if result["abstained"]:
    print("Not scored: paste at least 50 words.")
else:
    print(result["verdict"], result["fraction_ai"], result["run"])

Node

const response = await fetch("https://human.olive.is/api/v1/detect", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.HUMAN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ text, sensitivity: "standard" }),
});

const result = await response.json();
if (result.abstained) {
  console.log("Not scored: paste at least 50 words.");
} else {
  console.log(result.verdict, result.fraction_ai, result.run);
}

Timeouts

The first check after a quiet spell can take longer, while the detector starts. The server gives up on the detector within 120 seconds and answers with an error, so set your own timeout to at least that. The server does not retry for you.

Errors

Every error is a JSON body with an error field.

400 {"error":"bad_request"}: the body was not JSON, or text was missing or not a string.

401: the key was refused. The error field gives the reason in one word: missing, malformed, wrong-key-id, bad-signature, expired, revoked or wrong-scope. Fix the key rather than retrying. wrong-scope means the key was made for a different service.

413 {"error":"too_long","maxChars":200000}: the text is over 200,000 characters. That limit is the same on every plan.

422 {"error":"too_short","minWords":50}: the text is under 50 words. Send a longer passage.

429 {"error":"rate_limited","resetAt":<unix ms>}: an hourly limit below was reached. The retry-after header gives the wait in seconds.

502 or 503 {"error":"not_scored"}: the detector could not be reached, or this deployment has no detector set up. You get "not scored", never a score from anywhere else, and the server does not retry. Retry when you choose to.

Rate limits

API usage is not metered or billed yet. No word count is kept against a key, no invoice is produced, and the plan allowances on the pricing page are not enforced on this endpoint.

What is enforced is an hourly limit, so that one key cannot run the service without end: 60 requests and 120,000 words per key, and 120 requests per IP address, each counted over the last hour. Over a limit, the answer is 429 with a retry-after header. The counts are kept in each running server instance, not in a shared store, so in practice each limit applies per instance.

Education accounts

An email address at a recognised school or university domain gets the Education plan free, including the API and the MCP server, and 1,000 papers a month in the web app. The endpoint, the key format and the response are the same as on any other plan.

Verify the address before you create a key. Until it is verified, the account is on the Free plan, and creating a key answers 403 {"error":"plan_required"}.

What it reads

The detector reads English academic and student writing. It is not built or tested for other languages or other kinds of writing. From 50 to 149 words, a reading is weak. Machine-written text under 300 words has not been measured.

A flag from our detector is a reason to look closer. It is not proof that anyone cheated. Do not build a system that penalises a person on a verdict alone.