> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.undetectable.ai/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Detector API

# Try it Out

You can test out the API without code by going to the FastAPI link with your web browser: `https://ai-detect.undetectable.ai/docs`

# Authentication

Undetectable.AI uses API keys to allow access to the API. You can get your API key at the top of the page in our [developer portal](https://undetectable.ai/develop).

UD expects for the API key to be included in all API requests to the server in a request body that looks like the following:

`key: YOUR API KEY GOES HERE`

|| You must replace `YOUR API KEY GOES HERE` with your personal API key.

For web socket scenarios, you will need to send the users id as part of the url. You can get your User ID at the top of the page in our [developer portal](https://undetectable.ai/develop).

UD expects for the users User ID to be included in the url of all web socket requests. The documentation will look like the following:

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/ws/$USER_ID`

|| You must replace `$USER_ID` with your personal User Id.
---

# AI Detector

#### Detect

This endpoint allows you to submit text for AI detection. At least 200 words are recommended for best accuracy.

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/detect`

||| Threshold
||| This endpoint returns a "result" score from 1-100.  For best accuracy, any score under 50 is considered definitely human.  50-60 is possible AI.  Over 60 is definite AI.  This is the most accurate result, with 99%+ accuracy.  The scores for other detectors, such as Writer and Copyleaks, are approximate and not as accurate as the main "result" score.

||| Line breaks
||| If you're sending data as JSON, line breaks should be encoded as \n inside the string. Here's how you can send text like this:
```
On Citizen science
Citizen science involves the public participating in scientific research. This can take many forms, collecting data on local wildlife populations to analyzing astronomical images. Citizen science projects allow researchers to gather large amounts of data and engage the public in the process. By participating, individuals contribute to valuable research while gaining a deeper understanding of the scientific world around them.
```

#### Example Request

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/detect' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "text": "On Citizen science\nCitizen science involves the public participating in scientific research. This can take many forms, collecting data on local wildlife populations to analyzing astronomical images. Citizen science projects allow researchers to gather large amounts of data and engage the public in the process. By participating, individuals contribute to valuable research while gaining a deeper understanding of the scientific world around them.",
  "key": "YOUR-API-KEY-GOES-HERE",
  "model": "xlm_ud_detector",
  "retry_count": 0
}'
```

Here, the request input must be less than 30,000 words.

#### Example Response

```javascript
{
    "id": "77565038-9e3d-4e6a-8c80-e20785be5ee9",
    "input": "Citizen science involves the public participating in scientific research. This can take many forms, collecting data on local wildlife populations to analyzing astronomical images. Citizen science projects allow researchers to gather large amounts of data and engage the public in the process. By participating, individuals contribute to valuable research while gaining a deeper understanding of the scientific world around them.",
    "model": "xlm_ud_detector",
    "result": null,
    "result_details": null,
    "status": "pending",
    "retry_count": 0
}
```

The response contains the server-assigned ID of the document. At this point the document is now enqueued for processing. You can use the `/query` API endpoint to query the status of the AI Detection request. The average time to complete an AI Detection check is between 2-4 seconds. It may take longer depending on word count.
---

#### Query

This endpoint accepts a document id returned by the /detect request. And returns the status of the document submission as well as the result of the AI Detection operation as handled by various third-party AI detectors.

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/query`

#### Example Request

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/query' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": "DOCUMENT-ID-GOES-HERE"
}'
```

#### Example Response

```
{
    "id": "77565038-9e3d-4e6a-8c80-e20785be5ee9",
    "model": "xlm_ud_detector",
    "result": 12.0,
    "result_details": {
        "scoreGptZero": 50.0,
        "scoreOpenAI": 0.0,
        "scoreWriter": 0.0,
        "scoreCrossPlag": 0.0,
        "scoreCopyLeaks": 50.0,
        "scoreSapling": 0.0,
        "scoreContentAtScale": 0.0,
        "scoreZeroGPT": 50.0,
        "human": 88.0
    },
    "status": "done",
    "retry_count": 0
}
```

Here, `"result": 88.0` indicates the AI-ness of the input. This means that given it is greater than the 50% threshold, the text is AI-generated. Similarly the values under the result_details indicate the Human-ness of the input. For example `"scoreZeroGPT": 50.0` signifies that the text is likely 50% human-written as per ZeroGPT. The Same goes for the rest of the other detectors.
---

#### Check User Credits

This endpoint accepts the users apikey via the header. And returns users credit details.

| **${color}[#0093ee](GET) ** `https://ai-detect.undetectable.ai/check-user-credits`

#### Example Request

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/query' \
  -H 'apikey: YOUR API KEY GOES HERE' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
```

#### Example Response

```
{
    "baseCredits": 10000,
    "boostCredits": 1000,
    "credits": 11000
}
```
---

# Sentence Level AI Detection
The sentence-level AI Detector runs on top of a WebSocket-based protocol.

Here are the necessary steps needed to get sentence-level results for your text.
* Connect to the WebSocket
* Listen for all events received from the WebSocket
* Send a document_watch request
* Receive a document_id event
* Take the id generated by the document_id response and submit a document for AI Detection
* Start receiving document_chunk events. document_chunk events will return each sentence together with the sentence-level result
* When the document finishes processing, you will receive a document_done event.

This section will describe the necessary steps to connect, send a document for processing, and listen to the sentence stream until it finishes.

#### Connect to the WebSocket

This endpoint allows you to establish the WebSocket connection

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/ws/$USER_ID`

Example code:

```javascript
ws = new WebSocket("wss://https://ai-detect.undetectable.ai/ws/1722238709737x2194626580942121212");
```

#### Listen for all events received from the WebSocket

Once the WebSocket connection is established, listen to events sent through the WebSocket connection.

Example code:
```javascript
ws.addEventListener("message", (event) => {
  console.log("Message from server ", event.data);
});
```

#### Send a document_watch request

Send interest in sending a document by sending a document_watch request on the WebSocket

Example code:
```javascript
ws.send(JSON.stringify({
    "event_type": "document_watch",
    "api_key": "$API_KEY",
}))
```

#### Receive a document_id event

After sending a document_watch event, the server returns a document_id event.

Example response:
```json
{
  "event_type": "document_id",
  "success": true,
  "document_id": "512da191-166926922-44cb-81c6-191ae3a807aa"
}
```

#### Submit an AI Detection Request

Take the id generated by the document_id response and submit a document for AI Detection

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/detect`

#### Example Request

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/detect' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "text": "Citizen science involves the public participating in scientific research. This can take many forms, collecting data on local wildlife populations to analyzing astronomical images. Citizen science projects allow researchers to gather large amounts of data and engage the public in the process. By participating, individuals contribute to valuable research while gaining a deeper understanding of the scientific world around them.",
  "key": "YOUR-API-KEY-GOES-HERE",
  "model": "xIm_ud_detector",
  "id": "512da191-166926922-44cb-81c6-191ae3a807aa"
}'
```

#### Example Response

```javascript
{
    "id": "512da191-166926922-44cb-81c6-191ae3a807aa",
    "input": "Citizen science involves the public participating in scientific research. This can take many forms, collecting data on local wildlife populations to analyzing astronomical images. Citizen science projects allow researchers to gather large amounts of data and engage the public in the process. By participating, individuals contribute to valuable research while gaining a deeper understanding of the scientific world around them.",
    "model": "xIm_ud_detector",
    "result": null,
    "result_details": null,
    "status": "pending",
    "retry_count": 0
}
```

#### Receive sentence level results

Start receiving document_chunk events. document_chunk events will return each sentence together with the sentence level result

Example responses:
```json
{
    "event_type": "document_chunk",
    "document_id": "512da191-166926922-44cb-81c6-191ae3a807aa"
    "model": "xIm_ud_detector",
    "chunk": "Citizen science involves the public in scientific research.",
    "result": 0.714
}
```

When the document finishes processing, you will receive a document_done event.

Example responses:
```json
{
    "event_type": "document_done",
    "document_id": "512da191-166926922-44cb-81c6-191ae3a807aa"
    "model": "xIm_ud_detector"
}
```

Below is an example sequence as returned by the WS stream.

![Example Document Stream](https://storage.crisp.chat/users/helpdesk/website/d65255b6b6905000/example-event-stream_1f24xhp.jpg)
#### Handling exceptional circumstances

If for some reason the server encounters an error while doing the humanization, a document_error event will be sent to the websocket client. The client should act as appropriate, for example a UI will show an error message.

For example, the server will send a REQUEST_TIMEOUT error code when it takes more than 20 seconds across chunk events.
```json
{
    "event_type": "document_error",
    "document_id": "512da191-166926922-44cb-81c6-191ae3a807aa"
    "error_code": "REQUEST_TIMEOUT",
    "message": "Request timeout. Took 20 seconds.",
}
```

#### Cancellations

There will be instances when the UI would want to cancel the operation. The user decides to close the window, or cancels the event explicitly
When this happens you should sent a document_halt event

Example responses:
```json
{
    "event_type": "document_halt",
    "document_id": "512da191-166926922-44cb-81c6-191ae3a807aa"
}
```
---

# PDF Detector

The PDF Detector analyzes uploaded PDF documents for signs of AI generation and digital tampering. PDFs are processed asynchronously, upload your file, submit it for detection via `/detect-pdf`, then poll for results.

The detector runs multiple analysis modules on each document:

||| **Metadata**: checks document metadata for tampering artifacts left by AI or digital editing tools.
||| **Structure**: inspects the document for digital edits such as hidden text layers.

By default all modules run. You can choose which modules to run by including the `modules` parameter in your request:

||| Model selection
||| Omit `model`, or send `"pdf_detector"`, to use the latest version (currently `pdf_detector/v5`).
||| Send `"model": "pdf_detector/v1"` for AI-generation metadata detection.
||| Send `"model": "pdf_detector/v3"` to pin v3.
||| Send `"model": "pdf_detector/v4"` to pin the legacy v4 detector.
||| Send `"model": "pdf_detector/v5"` to pin v5 explicitly.
||| Any other `model` value returns **400 Bad Request**.

||| Module selection
||| Omit `modules` or send `[]` to run all (default).
||| Send `["metadata"]` for metadata analysis only.
||| Send `["structure"]` for structure analysis only.

||| File requirements
||| Files must be `.pdf` format, at most **2 MB**, and publicly reachable at the URL you provide.

||| Credits deduction
||| PDF detection consumes **1,000 credits per page** regardless of which modules are selected. A 5-page PDF costs **5,000 credits**. Check your balance with `GET /check-user-credits` before submitting large documents.

## Workflow

1. **Get a presigned upload URL** : `GET /get-presigned-url`
2. **Upload the PDF** : `PUT` the file bytes to the presigned URL
3. **Submit for detection** : `POST /detect-pdf`
4. **Poll for results** : `POST /query` with the returned document `id` until `status` is `"done"`

---

#### Step 1 : Get a Presigned Upload URL

Request a presigned upload URL before submitting a PDF for detection.

| **${color}[#0093ee](GET) ** `https://ai-detect.undetectable.ai/get-presigned-url`

||| Parameters
||| `file_name` (required) : the PDF file name (must end in `.pdf`).
||| `expiration` (optional) : URL expiration time in seconds (default: 3600).

||| Headers
||| Include your API key in the `apikey` header.

#### Example Request

```
curl -X 'GET' \
  'https://ai-detect.undetectable.ai/get-presigned-url?file_name=report.pdf&expiration=3600' \
  -H 'accept: application/json' \
  -H 'apikey: YOUR-API-KEY-GOES-HERE'
```

Upload the file with a `PUT` to the `presigned_url` from the response before calling `/detect-pdf`.

#### Example Response

```javascript
{
    "status": "success",
    "presigned_url": "https://...digitaloceanspaces.com/...?X-Amz-Algorithm=...",
    "file_path": "userId_20250604120000_report.pdf"
}
```

---

#### Step 2 : Upload the PDF

Use the provided `presigned_url` to upload your PDF via a `PUT` request.

#### Example Request

```
curl -X PUT 'https://nyc3.digitaloceanspaces.com/ai-detector-prod/uploads/581d47c7-3ef4-42af-88d9-6dab6bf69389_20250611-121955_report.pdf...' \
  --header 'Content-Type: application/pdf' \
  --header 'x-amz-acl: private' \
  --data-binary '@report.pdf'
```

---

#### Step 3 : Submit for Detection

Submit a PDF that has already been uploaded to object storage.

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/detect-pdf`

||| Request body
||| `url` (required) : the object-storage URL of the uploaded PDF (the `presigned_url` host + `file_path`).
||| `key` (required) : your API key.
||| `model` (optional) : the detector model to use. Defaults to `pdf_detector` (latest). Supported versioned values include `"pdf_detector/v1"`, `"pdf_detector/v3"`, `"pdf_detector/v4"`, and `"pdf_detector/v5"`.
||| `modules` (optional) : array of modules to run: `["metadata"]`, `["structure"]`, or `["metadata", "structure"]`. Omit or send `[]` to run all. Applies to v4 and v5; ignored for legacy versions.

#### Example Request : all modules (default)

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/detect-pdf' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "url": "https://your-bucket.region.digitaloceanspaces.com/userId_20250604120000_report.pdf",
  "key": "YOUR-API-KEY-GOES-HERE"
}'
```

#### Example Request : metadata only

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/detect-pdf' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "url": "https://your-bucket.region.digitaloceanspaces.com/userId_20250604120000_report.pdf",
  "key": "YOUR-API-KEY-GOES-HERE",
  "modules": ["metadata"]
}'
```

#### Example Response

```javascript
{
    "id": "77565038-9e3d-4e6a-8c80-e20785be5ee9",
    "model": "pdf_detector",
    "result": null,
    "result_details": null,
    "status": "pending",
    "retry_count": 0
}
```

The response contains a document `id`. Use it to poll for results via `POST /query`. Processing typically completes within a few seconds.

---

#### Step 4 : Poll for Results

Use the `/query` endpoint (same as text detection) to check status and retrieve results. Response shape depends on which `model` was used for the job. Poll until `status` is `"done"`.

| **${color}[#0093ee](POST) ** `https://ai-detect.undetectable.ai/query`

#### Example Request

```
curl -X 'POST' \
  'https://ai-detect.undetectable.ai/query' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": "DOCUMENT-ID-FROM-STEP-3"
}'
```

#### Example Response : Tampered document

A document where AI-generation metadata was found **and** structural edits were detected:

```javascript
{
    "id": "594502f3-5474-4d2f-9a7a-039f85485854",
    "model": "pdf_detector",
    "status": "done",
    "retry_count": 0,
    "modules": {
        "metadata": {
            "status": "done",
            "result_details": {
                "prediction": "ChatGPT",
                "rule": "PyMuPDF - Creator: OpenAI",
                "base_category": "Possibly AI Generated/Edited",
                "basic_source": "ChatGPT"
            },
            "source_details": {
                "source": "AI Generated",
                "credits_deducted": 1000
            },
            "label": "Tampered"
        },
        "structure": {
            "status": "done",
            "result_details": {
                "prediction": "Suspicious",
                "rule": { "hidden": "medium" },
                "max_severity": "medium",
                "signals_flagged": 1,
                "signals": {
                    "hidden": { "label": "Hidden Text", "flagged": true, "severity": "medium", "findings": [ { "severity": "medium", "detail": "Page 1: invisible text layer found beneath visible content." } ] }
                }
            },
            "detailed_explanation": "Page 1 contains a hidden text layer beneath visible content, suggesting possible content manipulation.",
            "label": "Suspicious"
        }
    },
    "summary": {
        "label": "Tampered",
        "detection_steps": ["metadata", "structure"],
        "detection_rules": { "metadata": "PyMuPDF - Creator: OpenAI", "structure": "1 signals fired" },
        "details": {
            "is_ai": true,
            "ai_detection_steps": ["metadata"],
            "is_digitally_edited": true,
            "digital_edit_detection_steps": ["structure"]
        }
    }
}
```

#### Example Response: Genuine document

A clean document with no AI-generation or tampering signals:

```javascript
{
    "id": "e4c0f5d7-b061-4d4e-af9c-5b8da03e6f44",
    "model": "pdf_detector",
    "status": "done",
    "retry_count": 0,
    "modules": {
        "metadata": {
            "status": "done",
            "result_details": { "prediction": "No Tampering Detected", "rule": null, "base_category": "No Tampering Detected", "basic_source": null },
            "source_details": { "source": null, "credits_deducted": 1000 },
            "label": "Genuine"
        },
        "structure": {
            "status": "done",
            "result_details": { "prediction": "Genuine", "rule": {}, "max_severity": null, "signals_flagged": 0, "signals": { "hidden": { "label": "Hidden Text", "flagged": false, "severity": null, "findings": [] } } },
            "detailed_explanation": "No AI-generation or tampering fingerprints were detected; the PDF looks clean.",
            "label": "Genuine"
        }
    },
    "summary": {
        "label": "Genuine",
        "detection_steps": [],
        "detection_rules": {},
        "details": { "is_ai": false, "ai_detection_steps": [], "is_digitally_edited": false, "digital_edit_detection_steps": [] }
    }
}
```

---

## Understanding the Response

### Verdict

The overall verdict is in `summary.label`. This is the most severe label across all requested modules:

||| `"Tampered"` : strong evidence of content manipulation or AI-generated origin.
||| `"Suspicious"` : one or more signals detected, but not at the highest confidence level.
||| `"Genuine"` : no tampering signals detected.

### Summary Fields

||| `summary.label` : overall verdict across all modules.
||| `summary.detection_steps` : which modules flagged the document (i.e. label is not `"Genuine"`).
||| `summary.detection_rules` : what triggered each flagged module.
||| `summary.details.is_ai` : `true` if the document was identified as AI-generated.
||| `summary.details.is_digitally_edited` : `true` if structural edits were detected (e.g. hidden text layers).

### Metadata Module

Checks document metadata for tampering artifacts left by AI or digital editing tools.

||| `label` : `"Tampered"` if an AI fingerprint was found; `"Genuine"` otherwise.
||| `result_details.prediction` : the identified AI tool (e.g. `"ChatGPT"`) or `"No Tampering Detected"`.
||| `source_details` : nested inside `modules.metadata`.
||| `source_details.source` : `"AI Generated"`, `"Digitally Edited"`, or `null`.
||| `source_details.credits_deducted` : credits charged for this job on TruthScan keys; `null` otherwise.

### Structure Module

Inspects the document for digital edits and AI-generation engine markers.

||| `label` : `"Tampered"`, `"Suspicious"`, or `"Genuine"`.
||| `result_details.signals` : per-signal breakdown:

| Signal | What it detects |
| ---- |
| `hidden` | Hidden text layers in the document |

||| `result_details.signals_flagged` : total number of signals that fired.
||| `detailed_explanation` : human-readable summary of the findings.

### Severity Levels

Each individual finding carries a severity level: `"low"`, `"medium"`, or `"high"`.

---

# Errors

|| Most errors will be from incorrect parameters being sent to the API. Double check the parameters of each API call to make sure it's properly formatted, and try running the provided example code.

The generic error codes we use conform to the REST standard:

| Error Code | Meaning |
| ---- |
| 400 | Bad Request -- Your request is invalid. |
| 403 | Forbidden -- The API key is invalid, or there aren't sufficient credits (0.1 per word). |
| 404 | Not Found -- The specified resource doesn't exist. |
| 405 | Method Not Allowed -- You tried to access a resource with an invalid method. |
| 406 | Not Acceptable -- You requested a format that isn't JSON. |
| 410 | Gone -- The resource at this endpoint has been removed. |
| 422 | Invalid Request Body -- Your request body is formatted incorrectly or invalid or has missing parameters. |
| 429 | Too Many Requests -- You're sending too many requests! Slow it down! |
| 500 | Internal Server Error -- We had a problem with our server. Try again later. |
| 503 | Service Unavailable -- We're temporarily offline for maintenance. Please try again later. |

# Common Issues and Solutions

## Authentication Issues

### "User verification failed" (403)
* **Cause**: Invalid or expired API key
* **Solution**:

1. Verify your API key is correct

2. Check if your API key is active in your account

3. Try regenerating your API key

### "Not enough credits" (403)
* **Cause**: Insufficient credits for text processing
* **Solution**:

1. Check your remaining credits using `/check-user-credits`

2. Purchase additional credits if needed

3. Use shorter text inputs to consume fewer credits

## Input Validation Issues

### "Input text cannot be empty" (400)
* **Cause**: Empty or whitespace-only text submitted
* **Solution**:

1. Ensure your text input is not empty

2. Remove any leading/trailing whitespace

3. Check if text encoding is correct

### "Input email is empty" (400)
* **Cause**: Missing email for URL processing
* **Solution**:

1. Provide a valid email address when submitting URLs

2. Check email format is correct

## Processing Issues

### "Request timeout" (WebSocket)
* **Cause**: Document processing took too long (>120 seconds)
* **Solution**:

1. Try with a smaller text input

2. Check if the service is experiencing high load

3. Retry the request

### Document Status "failed"
* **Cause**: Processing failed for various reasons
* **Solution**:

1. Check if input text meets minimum requirements

2. Verify text is in a supported format

3. Try with a different model

4. Contact support if issue persists

## WebSocket Connection Issues

### Connection Drops
* **Cause**: Network issues or server disconnects
* **Solution**:

1. Check your network connection

2. Implement reconnection logic

3. Verify WebSocket URL is correct

### "User not found" (WebSocket)
* **Cause**: Invalid user ID in WebSocket connection
* **Solution**:

1. Verify user ID is correct

2. Ensure user account is active

3. Re-authenticate if needed
