The AI Detection API allows organizations to evaluate the authenticity of written content by estimating the likelihood it was generated by AI tools. This API provides a score indicating the confidence level that a document was AI-generated and the percentage of text that appears AI-generated, helping organizations maintain trust, accountability, and responsible AI usage in their workflows. This AI Detection API developer guide explains how to create a score request, upload a document for scoring, and get the document scoring status and result. The API is intended for programmatic consumption and is accessible through an HTTP REST interface.
Integration Steps
Synchronous1.8s for up to 180s.succeeded / failed / cancelled.output_payload.assets[] URLs.Step 1 · Create Request
Submit generation input. Choose a backend language example below.
curl -X POST https://api.openoctopus.com/v1/documents/analyses \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer ooq_your_api_key" \\
-d '{
"model": "openoctopus/ai-detection",
"input": {
"file_url": "https://example.com/sample-document.docx",
"filename": "sample-document.docx"
}
}'Poll Task Status
Use the task ID from create response to poll execution status.
curl https://api.openoctopus.com/v1/tasks/task_id_from_previous_response \
-H "Authorization: Bearer ooq_your_api_key"const timeoutMs = 180000;
const intervalMs = 1800;
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const r = await fetch("https://api.openoctopus.com/v1/tasks/task_id_from_previous_response", {
headers: { Authorization: "Bearer " + process.env.OPENOCTOPUS_API_KEY },
});
const task = await r.json();
if (!r.ok) throw new Error(task?.error?.message ?? "task query failed");
if (task.status === "queued" || task.status === "processing") {
await new Promise((resolve) => setTimeout(resolve, intervalMs));
continue;
}
if (task.status === "succeeded") {
console.log(task.output_payload);
break;
}
throw new Error(task?.error_message ?? "task failed");
}import time, requests
timeout_s = 180
interval_s = 1.8
started = time.time()
while time.time() - started < timeout_s:
resp = requests.get(
"https://api.openoctopus.com/v1/tasks/task_id_from_previous_response",
headers={"Authorization": f"Bearer {OPENOCTOPUS_API_KEY}"},
timeout=30,
)
task = resp.json()
if resp.status_code >= 400:
raise RuntimeError(task.get("error", {}).get("message", "task query failed"))
status = task.get("status")
if status in ("queued", "processing"):
time.sleep(interval_s)
continue
if status == "succeeded":
print(task.get("output_payload"))
break
raise RuntimeError(task.get("error_message", "task failed"))Request Example (From Internal)
{
"model": "openoctopus/your-model",
"input": {
"text": "This is a sample text to analyze.",
"file": "https://example.com/document.pdf",
"website": "https://example.com",
"version": "4.14",
"sentences": "true",
"language": "en"
}
}Submit Response Example
{
"id": "00000000-0000-0000-0000-000000000000",
"status": "queued",
"model": "openoctopus/your-model"
}Input Schema (Standard + Provider Extension)
Standard request contract merged with this model's upstream input parameters.
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
| text | string | Yes | The text to scan. Minimum 300 characters, maximum 150,000 characters. | This is a sample text to analyze. |
| file | string | No | A publicly accessible URL to a file to scan. Must be .pdf, .doc, or .docx. | https://example.com/document.pdf |
| website | string | No | A website URL to scan. Must be publicly accessible. | https://example.com |
| version | string | No | Model version. Options: 4.14, 4.13, ..., latest. Default: 4.14. | 4.14 |
| sentences | boolean | No | Whether to include sentence-level scores. Default: true. | true |
| language | string | No | 2-letter language code. Default: auto. Supported: en, fr, es, pt, nl, de, pl, it, ro, id, tl, ru, bg, zh. | en |
{
"standard": {
"model": "public model slug (required)",
"prompt": "user prompt (required)",
"input": "provider-specific options (optional object)"
},
"providerExtension": {
"params": [
{
"name": "text",
"type": "string",
"example": "This is a sample text to analyze.",
"required": true,
"description": "The text to scan. Minimum 300 characters, maximum 150,000 characters.",
"exposedToCustomer": true
},
{
"name": "file",
"type": "string",
"example": "https://example.com/document.pdf",
"required": false,
"description": "A publicly accessible URL to a file to scan. Must be .pdf, .doc, or .docx.",
"exposedToCustomer": true
},
{
"name": "website",
"type": "string",
"example": "https://example.com",
"required": false,
"description": "A website URL to scan. Must be publicly accessible.",
"exposedToCustomer": false
},
{
"name": "version",
"type": "string",
"example": "4.14",
"required": false,
"description": "Model version. Options: 4.14, 4.13, ..., latest. Default: 4.14.",
"exposedToCustomer": false
},
{
"name": "sentences",
"type": "boolean",
"example": "true",
"required": false,
"description": "Whether to include sentence-level scores. Default: true.",
"exposedToCustomer": true
},
{
"name": "language",
"type": "string",
"example": "en",
"required": false,
"description": "2-letter language code. Default: auto. Supported: en, fr, es, pt, nl, de, pl, it, ro, id, tl, ru, bg, zh.",
"exposedToCustomer": true
}
]
}
}Output Schema (Standard + Provider Extension)
Standard output contract merged with provider extension fields.
Note: integrate against output_payload.assets[]. raw is optional and may be omitted by endpoint policy.
| Field | Type | Exposed | Description | Example |
|---|---|---|---|---|
| status | integer | Yes | HTTP status code. 200 means success. | 200 |
| score | integer | Yes | Human score between 0 and 100. Higher means more likely human-written. | 85 |
| sentences | object | Yes | Array of sentence objects with text and score. | [object Object] |
| input | string | Yes | Type of input scanned: text, file, or website. | text |
| attack_detected | object | Yes | Object with boolean properties for zero-width spaces and homoglyph attacks. | [object Object] |
| readability_score | integer | Yes | Readability score between 0 and 100. | 75 |
| credits_used | integer | Yes | Number of credits consumed. | 50 |
| credits_remaining | integer | Yes | Credits remaining after request. | 950 |
| version | string | Yes | Model version used. | 4.14 |
| language | string | Yes | Detected language code. | en |
{
"standard": {
"id": "task id",
"status": "queued | processing | succeeded | failed | cancelled",
"queue": {
"enabled": "whether OpenOctopus local queue is enabled for this model",
"position": "1-based queue position while queued, 0 while processing, null when unavailable",
"size": "current queued request count for this model",
"concurrency": "local concurrent upstream submissions allowed for this model",
"upstreamQueueSupported": "whether the upstream provider has a reliable queue",
"upstreamCancelSupported": "whether the upstream provider supports cancellation"
},
"asset_storage": {
"provider": "supabase | aliyun-oss | tencent-cos",
"inputBucket": "bucket used for uploaded reference media",
"outputBucket": "bucket used for generated media",
"custom": "true when this model overrides default system asset storage"
},
"capability": "image_generation | video_generation",
"output_payload": {
"format": "openoctopus.image.output.v1 | openoctopus.video.output.v1",
"assets": "normalized output assets",
"raw": "optional sanitized debug payload (may be omitted depending on endpoint policy)"
}
},
"providerExtension": {
"fields": [
{
"name": "status",
"type": "integer",
"example": "200",
"description": "HTTP status code. 200 means success.",
"exposedToCustomer": true
},
{
"name": "score",
"type": "integer",
"example": "85",
"description": "Human score between 0 and 100. Higher means more likely human-written.",
"exposedToCustomer": true
},
{
"name": "sentences",
"type": "object",
"example": "[object Object]",
"description": "Array of sentence objects with text and score.",
"exposedToCustomer": true
},
{
"name": "input",
"type": "string",
"example": "text",
"description": "Type of input scanned: text, file, or website.",
"exposedToCustomer": true
},
{
"name": "attack_detected",
"type": "object",
"example": "[object Object]",
"description": "Object with boolean properties for zero-width spaces and homoglyph attacks.",
"exposedToCustomer": true
},
{
"name": "readability_score",
"type": "integer",
"example": "75",
"description": "Readability score between 0 and 100.",
"exposedToCustomer": true
},
{
"name": "credits_used",
"type": "integer",
"example": "50",
"description": "Number of credits consumed.",
"exposedToCustomer": true
},
{
"name": "credits_remaining",
"type": "integer",
"example": "950",
"description": "Credits remaining after request.",
"exposedToCustomer": true
},
{
"name": "version",
"type": "string",
"example": "4.14",
"description": "Model version used.",
"exposedToCustomer": true
},
{
"name": "language",
"type": "string",
"example": "en",
"description": "Detected language code.",
"exposedToCustomer": true
}
]
}
}Final Output Example (Normalized)
{
"capability": "image_generation",
"status": "succeeded",
"output_payload": {
"format": "openoctopus.image.output.v1",
"assets": [
{
"type": "image",
"url": "https://example.com/result.png",
"mimeType": "image/png"
}
],
"raw": {
"status": "example",
"score": "example",
"sentences": "example",
"input": "example",
"attack_detected": "example",
"readability_score": "example",
"credits_used": "example",
"credits_remaining": "example",
"version": "example",
"language": "example"
}
}
}Step 4 · Error Handling Guide
Error codes below are loaded from internal gateway error definitions and should be treated as source of truth.
| Code | HTTP | Retryable | Category | Message |
|---|---|---|---|---|
| invalid_request | 400 | No | validation | The request payload is invalid. Check the required fields and try again. |
| unauthorized | 401 | No | auth | Authentication is required for this request. |
| invalid_api_key | 401 | No | auth | The API key is invalid or inactive. |
| insufficient_balance | 402 | No | billing | Your wallet balance is insufficient. Please top up and try again. |
| model_not_available | 404 | No | routing | The requested model is currently unavailable. |
| task_not_found | 404 | No | task | The requested task could not be found. |
| file_not_found | 404 | No | asset | The requested generated file is not available. |
| provider_offline | 503 | Yes | upstream | The selected model is temporarily unavailable. Please retry later. |
| provider_model_inactive | 503 | Yes | routing | The selected model is temporarily unavailable. Please retry later. |
| provider_credential_missing | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_incomplete | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_unusable | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_legacy | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_unavailable | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| provider_credential_decrypt_failed | 503 | Yes | system | The service is temporarily unavailable for this model. Please retry later. |
| model_billing_not_configured | 503 | No | system | The selected model is temporarily unavailable. Please retry later. |
| provider_pricing_not_configured | 503 | No | system | The selected model is temporarily unavailable. Please retry later. |
| database_operation_failed | 503 | Yes | system | The service could not access required internal records. Please retry later. |
| billing_resolution_failed | 503 | Yes | system | The selected model pricing could not be evaluated. Please retry later. |
| request_record_write_failed | 503 | Yes | system | The request could not be recorded internally. Please retry later. |
| api_key_touch_failed | 503 | Yes | system | The request was accepted but internal key tracking failed. Please retry later. |
| queue_unavailable | 503 | Yes | system | The internal job queue is temporarily unavailable. Please retry later. |
| provider_submit_failed | 502 | Yes | upstream | The generation provider could not accept the request. Please retry shortly. |
| provider_poll_failed | 502 | Yes | upstream | The generation provider could not complete the request. Please retry shortly. |
| upstream_failed | 502 | Yes | upstream | The generation provider failed to complete the request. Please retry shortly. |
| content_policy_violation | 400 | No | safety | The prompt or image was rejected by the provider safety policy. Please adjust the content and try again. |
| upstream_timeout | 504 | Yes | upstream | The generation request timed out. Please retry shortly. |
| upstream_result_missing | 502 | Yes | upstream | The generation provider returned an incomplete result. Please retry shortly. |
| video_output_missing | 502 | Yes | upstream | The generation provider returned an incomplete result. Please retry shortly. |
| service_unavailable | 503 | Yes | system | The service is temporarily unavailable. Please retry later. |
| internal_error | 500 | Yes | system | The service encountered an unexpected error. Please retry later. |
task.status=failed, always read error.code and match the table above; retry only when retryable=true.