Bulk API
curl --request POST \
--url https://text.external-api.pangram.com/bulk \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"items": [
{}
],
"text": [
{}
],
"model": "<string>"
}
'import requests
url = "https://text.external-api.pangram.com/bulk"
payload = {
"items": [{}],
"text": [{}],
"model": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"bulk_id": "<string>",
"status": "<string>",
"total_items": 123,
"accepted_items": [
{}
],
"failed_items": [
{}
],
"accepted": 123,
"succeeded": 123,
"failed": 123,
"created_at": "<string>",
"completed_at": "<string>",
"offset": 123,
"limit": 123,
"items": [
{}
]
}REST API
Bulk API
Submit asynchronous AI detection jobs for many texts
POST
/
bulk
Bulk API
curl --request POST \
--url https://text.external-api.pangram.com/bulk \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"items": [
{}
],
"text": [
{}
],
"model": "<string>"
}
'import requests
url = "https://text.external-api.pangram.com/bulk"
payload = {
"items": [{}],
"text": [{}],
"model": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"bulk_id": "<string>",
"status": "<string>",
"total_items": 123,
"accepted_items": [
{}
],
"failed_items": [
{}
],
"accepted": 123,
"succeeded": 123,
"failed": 123,
"created_at": "<string>",
"completed_at": "<string>",
"offset": 123,
"limit": 123,
"items": [
{}
]
}For more information on billing units and pricing, check our pricing page for developers.
Current
Completion time depends on the number and length of submitted items and current system load. Use
Terminal bulk statuses are
Or an
Do not include both
Example Response
Example Response
Example Response
Example Response
Current version
The Bulk API queues many AI detection inputs as one asynchronous job. Submit the job with
POST /bulk, poll GET /bulk/{bulk_id}, then page through item metadata or results.GET /bulk/{bulk_id} to monitor progress.
Bulk jobs use the same base URL and API key authentication as the AI detection task API:
https://text.external-api.pangram.com
succeeded, failed, and partial. Bulk metadata and results are retained for 48 hours after the job reaches a terminal status.
Timestamps are returned as Unix epoch seconds encoded as strings, such as "1760000000.0". Treat them as UTC instants when converting to a date-time value.
The launch bulk limit is 1,000 billable units per request. A billable unit is one started word block per valid item — 100 words for Pangram 4, or 1,000 words for Pangram 3 — with a minimum of one unit per item. There is no separate item-count limit, but normal request-body limits still apply. Requests over the current bulk limit return 413 Payload Too Large.
One model applies to the entire bulk job; per-item model selectors are not supported. Use GET /models to discover the selectors available to your API key. The REST API temporarily accepts an omitted selector for backward compatibility and resolves it to "default". New integrations should send it explicitly.
POST /bulk
Create a bulk AI detection job.POST https://text.external-api.pangram.com/bulk
Request
Provide exactly one ofitems or text.
Valid request bodies use either a plain text list:
{"text": ["First text", "Second text"], "model": "pangram-4"}
items list when you want customer IDs returned with status and results:
{"items": [{"id": "row-001", "text": "First text"}], "model": "pangram-4"}
text and items in the same request. Item id values are optional, but must be unique when provided.
array
List of item objects. Each item must include
text and may include a unique customer-defined id.array
List of input text strings. Use this simpler shape when you do not need customer item IDs.
string
default:"default"
A selector returned by
GET /models. Applies to every item in the job.Show Item object properties
Show Item object properties
Response
Returns202 Accepted.
string
The ID of the bulk job.
string
Initial status. Usually
queued; returns failed if every item failed immediate validation.integer
Total number of submitted items.
array
Items accepted for processing. Each item includes
index, optional id, and task_id.array
Items that failed immediate validation. Each item includes
index, optional id, task_id: null, stage, and error.Example
curl -X POST https://text.external-api.pangram.com/bulk \
-H "Content-Type: application/json" \
-H "x-api-key: your_api_key_here" \
-d '{
"items": [
{"id": "row-001", "text": "First text to analyze"},
{"id": "row-002", "text": "Second text to analyze"}
],
"model": "pangram-4"
}'
from pangram import Pangram
client = Pangram()
bulk = client.submit_bulk(
items=[
{"id": "row-001", "text": "First text to analyze"},
{"id": "row-002", "text": "Second text to analyze"},
],
model="pangram-4",
)
bulk_id = bulk["bulk_id"]
{
"bulk_id": "blk_123",
"status": "queued",
"total_items": 2,
"accepted_items": [
{
"index": 0,
"id": "row-001",
"task_id": "123e4567-e89b-12d3-a456-426614174000"
},
{
"index": 1,
"id": "row-002",
"task_id": "223e4567-e89b-12d3-a456-426614174000"
}
],
"failed_items": []
}
GET /bulk/
Fetch the current status and counters for a bulk job.GET https://text.external-api.pangram.com/bulk/{bulk_id}
Request
string
required
The bulk job ID returned by
POST /bulk.Response
string
The ID of the bulk job.
string
One of
queued, running, succeeded, failed, or partial.integer
Total number of submitted items.
integer
Number of items accepted for processing.
integer
Number of items that completed successfully.
integer
Number of items that failed.
string
Job creation timestamp as Unix epoch seconds encoded as a string.
string
Job completion timestamp as Unix epoch seconds encoded as a string.
null while the job is not terminal.{
"bulk_id": "blk_123",
"status": "partial",
"total_items": 3,
"accepted": 2,
"succeeded": 2,
"failed": 1,
"created_at": "1760000000.0",
"completed_at": "1760000030.0"
}
Status values
| Status | Description |
|---|---|
queued | The job was accepted, but no accepted item has started processing yet. |
running | At least one accepted item is in progress and the job is not terminal. |
succeeded | Every submitted item completed successfully. |
failed | Every submitted item failed, including immediate validation failures. |
partial | Every submitted item is terminal, with at least one success and at least one failure. |
GET /bulk//items
Fetch paginated item metadata for a bulk job.GET https://text.external-api.pangram.com/bulk/{bulk_id}/items?offset=0&limit=100
Request
string
required
The bulk job ID returned by
POST /bulk.integer
default:"0"
Zero-based item offset.
integer
default:"100"
Maximum number of items to return. The maximum is
1000.Response
string
The ID of the bulk job.
integer
The returned page offset.
integer
The returned page limit.
integer
Total number of submitted items.
array
Item metadata. Each item includes
index, optional id, task_id, stage, and optional error.{
"bulk_id": "blk_123",
"offset": 0,
"limit": 100,
"total_items": 2,
"items": [
{
"index": 0,
"id": "row-001",
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"stage": "STAGE_SUCCESS",
"error": null
},
{
"index": 1,
"id": "row-002",
"task_id": null,
"stage": "STAGE_FAILED",
"error": "Text must contain at least one valid token"
}
]
}
GET /bulk//results
Fetch paginated results for a bulk job.GET https://text.external-api.pangram.com/bulk/{bulk_id}/results?offset=0&limit=100
Request
string
required
The bulk job ID returned by
POST /bulk.integer
default:"0"
Zero-based item offset.
integer
default:"100"
Maximum number of items to return. The maximum is
1000.Response
string
The ID of the bulk job.
integer
The returned page offset.
integer
The returned page limit.
integer
Total number of submitted items.
array
Result items for successful or in-progress work. Successful completed items include
result; in-progress items have result: null.array
Failed item metadata for the requested page.
{
"bulk_id": "blk_123",
"offset": 0,
"limit": 100,
"total_items": 2,
"items": [
{
"index": 0,
"id": "row-001",
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"stage": "STAGE_SUCCESS",
"error": null,
"result": {
"stage": "STAGE_SUCCESS",
"text": "First text to analyze",
"version": "4.0",
"prediction": "We believe that this entire text is human-written.",
"prediction_short": "Human",
"fraction_ai": 0.0,
"fraction_ai_assisted": 0.0,
"fraction_human": 1.0,
"headline": "Human Written",
"num_ai_segments": 0,
"num_ai_assisted_segments": 0,
"num_human_segments": 1,
"windows": [
{
"text": "First text to analyze",
"label": "Human Written",
"ai_assistance_score": 0.02,
"confidence": "High",
"start_index": 0,
"end_index": 21,
"word_count": 4,
"token_length": 5,
"is_humanized": false,
"humanizer_score": 0.0
}
]
}
}
],
"failed_items": [
{
"index": 1,
"id": "row-002",
"task_id": null,
"stage": "STAGE_FAILED",
"error": "Text must contain at least one valid token"
}
]
}
Errors
| Status Code | Description |
|---|---|
401 Unauthorized | The x-api-key is missing or invalid. |
402 Payment Required | The account has insufficient credits. |
403 Forbidden | The requested model is not enabled for the API key, or the API key does not own the requested bulk job. |
404 Not Found | The requested bulk job does not exist. |
413 Payload Too Large | The bulk request exceeds the maximum billable units. |
422 Unprocessable Entity | The request is empty, contains both items and text, includes duplicate item IDs, uses an invalid model selector, or otherwise fails validation. |
500 Internal Server Error | There was an error processing the request. |
503 Service Unavailable | The requested model is temporarily unavailable. |